2017-01-02 2 views
3

Следующий вызов завершится неудачно, потому что компилятор ожидает метод 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); 

Таким образом, компилятор не может использовать эту перегрузку любым способом. он не может отличить int до object.

Имея это в виду, почему следующий вызов неоднозначен?

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); 

Если компилятор не может использовать перегрузку с объектом в любом случае, то почему он здесь борется?


Вот фактический код, который у меня есть. Я могу справиться с этой проблемой легко, но я просто занимаюсь любопытством.

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); 
+1

Действительно, [mcve] облегчит вам помощь здесь. –

+0

'(x, i) => SetAll (x, i)' работает. Связано ли это с этим? –

+0

ну, я хочу использовать перегрузку без объекта, так что это будет '(x) => SetAll (x)', но компилятор просто не может понять это ... хотел использовать группы методов, но ....: P @PatrickHofman –

ответ

3

Это потому, что System.Func<in T1, in T2, out TResult> является контравариантным в своих типах аргументов. Это обозначается модификатором in по соответствующим параметрам типа. Это означает, что это соответствие для любой функции, принимающей аргумент типа T1, или любой тип T1 может быть назначен, а аргументу типа T2 или любому типу T2 может быть назначен. Ваша первая подпись соответствует перегрузке Enumerable.Select, которая не включает индекс. Однако ваша вторая подпись фактически соответствует перегрузке Enumerable.Select, которая включает индекс, потому что int назначается object.

Чтобы это продемонстрировать. Просто создайте произвольный класс и измените свою программу так.

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

class A {} 

Вы заметите ошибку исчезает int не присваиваемые A.

Как уже говорилось в комментариях, есть морщина, которую я не смог принять во внимание. Контравариантное имеет место соотношения между ссылочными типами и между генерик, не стесненными быть типами значений, но конкретно не работает при непосредственном назначении между делегатами с int и object Учитывая

Func<int, Action> f; 

Func<object, Action> g; 

Ниже оба ошибки

g = f; 
f = g; 

Однако, если заменить int с сказать некоторый класс A

Func<A, Action> f; 

Func<object, Action> g; 

Первая ошибка, потому что объект не является A, но второй преуспевает, как объяснялось выше.

g = f; 
f = g; 
+1

интересно ковариантные работы из 'Func ' to 'Func ' Но контравариант не работает. Я не могу использовать 'Func ' to 'Func '? но то, что вы сказали, имеет смысл для меня. (все еще не получил его полностью). Я думал, что его ошибка компилятора, но, по-видимому, его нет. –

+0

Причина, по которой вы не можете выполнить второе назначение, заключается в том, что это позволит использовать функцию, которая требует, чтобы 'int' передавался' object'. Есть такие языки, как Dart, которые позволяют это, но это строго считается необоснованным. Для справки, что поведение называется бивариантностью. –

+0

На самом деле, я думаю, что неправильно понял. Обновление ответа с использованием переменных. –

0

Для того, чтобы работать с группой методов, вы можете использовать фиктивную переменную.

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 

Тогда это будет работать

infos.Select(SetAll).ToArray(); 

Он будет использовать Select с индексатор, но оно не должно имеет большое значение.

Смежные вопросы