3

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);
M.kazem Akhgary
  • 18,645
  • 8
  • 57
  • 118

2 Answers2

3

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;
M.kazem Akhgary
  • 18,645
  • 8
  • 57
  • 118
Aluan Haddad
  • 29,886
  • 8
  • 72
  • 84
  • 1
    interestingly covariant works from `Func` to `Func` But contravariant does not work. I cant cast `Func` to `Func`? but what you said kinda makes sense to me. (still didn't get it completely) I thought its compiler bug but apparently its not – M.kazem Akhgary Jan 02 '17 at 16:13
  • The reason you can't perform the second assignment is that this would allow a function which requires an `int` to be passed an `object`. There are languages like Dart which do allow this but it's strictly considered to be unsound. For reference that behavior is called bivariance. – Aluan Haddad Jan 02 '17 at 16:17
  • Actually I think I misunderstood. Updating answer with example using variables. – Aluan Haddad Jan 02 '17 at 16:23
  • Very interesting. contravariant is possible with non-primitive types. for example from `Func` to `Func`... So I think this is something that is prevented by the compiler. but the rule exist when choosing method overloads. thanks Ill look into bivariance too – M.kazem Akhgary Jan 02 '17 at 16:28
0

In order to work with method group you can wrtie dummy variable.

private Action SetAll(PropertyInfo info, object obj) => () => info.SetValue(this, obj);
private Action SetAll(PropertyInfo info, int _) => () => info.SetValue(this, null); 
//                                           ^ this is dummy but lets you use methodgroup

Then this will work

infos.Select(SetAll).ToArray(); 

It will use Select with indexer but it shouldnt matter much.

M.kazem Akhgary
  • 18,645
  • 8
  • 57
  • 118