Почему `Predicate <T>` не соответствует `Func <T, bool>`?
Я пытаюсь скомпилировать следующий код в С#:
public static T FirstEffective(IEnumerable<T> list)
{
Predicate<T> pred = x => x != null;
return Enumerable.FirstOrDefault(list, pred);
}
В компиляторе (Mono/.NET 4.0) появляется следующая ошибка:
File.cs(139,47) The best overloaded method match for `System.Linq.Enumerable.FirstOrDefault<T>(this System.Collections.Generic.IEnumerable<T>,System.Func<T,bool>)' has some invalid arguments
/usr/lib/mono/4.0/System.Core.dll (Location of the symbol related to previous error)
File.cs(139,47): error CS1503: Argument `#2' cannot convert `System.Predicate<T>' expression to type `System.Func<T,bool>'
Это довольно странно, так как Predicate<T>
на самом деле является функцией, которая принимает в качестве входного параметра параметр T
и возвращает a bool
(T
является четным "ковариантным", поэтому разрешена специализация T
). Не учитывают ли делегаты "принцип замещения Лискова", чтобы получить, что Predicate<T>
эквивалентно Func<T,bool>
? Насколько я знаю, эта проблема эквивалентности должна быть разрешимой.
Ответы
Ответ 1
Спецификация С# ясно говорит о том, что:
15.1 Объявления делегатов
Типы делегатов в С# эквивалентны именам, а не структурно эквивалентны. В частности, два разных типа делегатов, которые имеют одинаковые списки параметров и тип возврата считаются разными делегатами Типы.
Вот почему ваш код не компилируется.
Вы можете заставить его работать, вызвав делегата, вместо его передачи:
public static T FirstEffective (IEnumerable<T> list) {
Predicate<T> pred = x => x != null;
return Enumerable.FirstOrDefault (list, x => pred(x));
}
Обновление
Существует отличная запись в блоге Эрика Липперта: бывший член команды С# в качестве Microsoft, который очень подробно отвечает на ваш вопрос: Делегаты и структурная идентичность.
Ответ 2
Типы делегатов неявно конвертируются, даже если они имеют все те же параметры и возвращаемую информацию. Тем не менее, есть простой способ обхода вашего дела. Вы можете использовать метод .Invoke
для вашего экземпляра делегата.
public static T FirstEffective<T>(IEnumerable<T> list)
{
Predicate<T> pred = x => x != null;
return Enumerable.FirstOrDefault(list, pred.Invoke);
}
Что касается вопроса о том, почему делегаты работают таким образом, ответ заключается в том, что это было дизайнерское решение. Классы, имеющие одинаковые общедоступные интерфейсы, также неявно конвертируемы, поэтому они не противоречат друг другу.
Ответ 3
Довольно запоздалый, но случайно я наткнулся на тот же вопрос, и точный ответ можно найти здесь: важный комментарий
В основном это означает, что это несогласованность, основанная на неудачном решении реализовать ее таким образом. Пока predicate<T> == func<T, bool>
, они разные, несмотря на одну и ту же подпись. Я полагаю, что по соображениям обратной совместимости можно преобразовать выражение и/или лямбда, а затем вернуть предикат через new predicate<T>(func<T, bool>)
.