Search code examples
c#.netambiguous-call

Why following call is ambiguous?


The following call will fail because compiler expects method SetAll(PropertyInfo, int).

var infos = GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);

var setters = infos.Select(SetAll); // no overload matches delegate.

private Action SetAll(PropertyInfo info, object obj) => () => info.SetValue(this, obj);

So that means compiler can't use this overload any way. it can't cast int to object.

Having this in mind, Why the following call is ambiguous?

var b = infos.Select(SetAll); // ambiguous between Select<PropertyInfo, int, Action>
                              //              and  Select<PropertyInfo, Action>

private Action SetAll(PropertyInfo info, object obj) => () => info.SetValue(this, obj);

private Action SetAll(PropertyInfo info) => () => info.SetValue(this, null);

If compiler can't use overload with object any way then why its struggling here?


Here is the actual code I'm having. I can deal with this problem easily but im just being curios.

var infos = GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);

if (useDefaultsOnReset)
{
    var defaults = infos.Select(GetAll);
    _resetters = infos.Zip(defaults, SetAll).ToArray();
}
else
{
    _resetters = infos.Select(SetAll).ToArray(); // error
}

private object GetAll(PropertyInfo info) => info.GetValue(this);
private Action SetAll(PropertyInfo info, object obj) => () => info.SetValue(this, obj);
private Action SetAll(PropertyInfo info) => () => info.SetValue(this, null);

Solution

  • This is because the System.Func<in T1, in T2, out TResult> is contravariant in its argument types. This is denoted by the in modifier on the corresponding type parameters. What this means is that it is a match for any function taking an argument of type T1 or any type T1 can be assigned to, and an argument of type T2 or any type T2 can be assigned to. Your first signature matches the overload of Enumerable.Select that does not incorporate an index. However, your second signature actually matches the overload of Enumerable.Select which does incorporate the index because int is assignable to object.

    To demonstrate this. Simply create an arbitrary class and change your program like so.

    private Action SetAll(PropertyInfo info, A a) => () => info.SetValue(this, obj);
    private Action SetAll(PropertyInfo info) => () => info.SetValue(this, null);
    
    class A {}
    

    You will observe the error disappears as int is not assignable to A.

    As discussed in the comments, there is a wrinkle I failed to take into account. The contravariant relationship holds between reference types and between generics that are not constrained to be value types but it specifically does not work in when directly assigning between delegates taking int and object Given

    Func<int, Action> f;
    
    Func<object, Action> g;
    

    The following are both errors

    g = f;
    f = g;
    

    However, if we replace int with say some class A

    Func<A, Action> f;
    
    Func<object, Action> g;
    

    The first is an error because an object is not an A, but the second succeeds as explained above.

    g = f;
    f = g;