Подтвердить что ты не робот

Почему `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>? Насколько я знаю, эта проблема эквивалентности должна быть разрешимой.

4b9b3361

Ответ 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>).