Почему Func <T, bool> вместо Predicate <T>?

211

Это просто вопрос любопытства, который меня интересовал, если бы у кого-нибудь был хороший ответ:

Например, в библиотеке классов .NET Framework есть два следующих метода:

public static IQueryable<TSource> Where<TSource>(
    this IQueryable<TSource> source,
    Expression<Func<TSource, bool>> predicate
)

public static IEnumerable<TSource> Where<TSource>(
    this IEnumerable<TSource> source,
    Func<TSource, bool> predicate
)

Почему они используют Func<TSource, bool>вместо Predicate<TSource>? Похоже, что Predicate<TSource>используется только List<T>и Array<T>, в то время Func<TSource, bool>как используется в значительной степени все Queryableи Enumerableметоды и методы расширения ... что с этим?

Svish
источник
21
Тьфу да, непоследовательное использование этих данных сводит меня с ума тоже.
Джордж Мауэр

Ответы:

170

Хотя Predicateбыло введено в то же время, что List<T>и Array<T>в .net 2.0, разные Funcи Actionварианты происходят из .net 3.5.

Таким образом, эти Funcпредикаты используются главным образом для согласованности в операторах LINQ. По состоянию на .net 3.5, об использовании Func<T>и Action<T>в Принципах :

Используйте новые типы LINQ Func<>и Expression<>вместо пользовательских делегатов и предикатов

Jb Evain
источник
7
Круто, никогда раньше не видел этих гильдий =)
Svish
6
Приму это как ответ, так как он имеет большинство голосов. и потому что у Джона Скита есть много представителей ...: p
Свиш
4
Это странное руководство, как написано. Конечно, он должен указывать «Использовать новые типы LINQ« Func <> »и« Action <> »[...]". Выражение <> - это совсем другое.
Джон Скит
3
Ну, на самом деле вы должны поместить это в контекст руководства, о написании поставщика LINQ. Так что да, для операторов LINQ рекомендуется использовать Func <> и Expression <> в качестве параметров для методов расширения. Я согласен, я был бы признателен за отдельное руководство по использованию Func и Action
Jb Evain
Я думаю, что смысл Скита в том, что выражение <> не является руководством. Это функция компилятора C #, которая распознает этот тип и делает с ним что-то другое.
Даниэль Эрвикер
116

Я думал об этом раньше. Мне нравится Predicate<T>делегат - это красиво и описательно. Однако вам необходимо учитывать перегрузки Where:

Where<T>(IEnumerable<T>, Func<T, bool>)
Where<T>(IEnumerable<T>, Func<T, int, bool>)

Это позволяет вам фильтровать на основе индекса записи. Это хорошо и последовательно, тогда как:

Where<T>(IEnumerable<T>, Predicate<T>)
Where<T>(IEnumerable<T>, Func<T, int, bool>)

не будет

Джон Скит
источник
11
Предикат <int, bool> был бы несколько уродлив - предикат обычно (IME компьютерной науки) основывается на одном значении. Конечно, это может быть предикат <Pair <T, int >>, но это еще страшнее :)
Джон Скит
так верно ... хе-хе. Нет, я думаю, что Func чище.
Свиш
2
Принято другой ответ из-за руководящих принципов. Хотел бы я отметить более одного ответа! Было бы неплохо, если бы я мог отметить ряд ответов как «Очень примечательный» или «Также имея хороший момент, который следует отметить в дополнение к ответу»
Свиш
6
Хм, только что увидел, что я написал этот первый комментарий неправильно: P мое предложение, конечно, будет предикатом <T, int> ...
Свиш
4
@JonSkeet Я знаю, что этот вопрос старый и все, но знаешь, что иронично? Имя параметра в Whereметод расширения predicate. Хех = П.
Конрад Кларк
32

Конечно, настоящая причина использования Funcвместо конкретного делегата заключается в том, что C # рассматривает отдельно объявленные делегаты как совершенно разные типы.

Несмотря на то, Func<int, bool>и Predicate<int>оба имеют одинаковые аргументов и возвращаемых типов, они не являются присваивание-совместимыми. Поэтому, если бы каждая библиотека объявила свой собственный тип делегата для каждого шаблона делегата, эти библиотеки не смогут взаимодействовать, если пользователь не вставит «соединяющие» делегаты для выполнения преобразований.

    // declare two delegate types, completely identical but different names:
    public delegate void ExceptionHandler1(Exception x);
    public delegate void ExceptionHandler2(Exception x);

    // a method that is compatible with either of them:
    public static void MyExceptionHandler(Exception x)
    {
        Console.WriteLine(x.Message);
    }

    static void Main(string[] args)
    {
        // can assign any method having the right pattern
        ExceptionHandler1 x1 = MyExceptionHandler; 

        // and yet cannot assign a delegate with identical declaration!
        ExceptionHandler2 x2 = x1; // error at compile time
    }

Призывая всех использовать Func, Microsoft надеется, что это облегчит проблему несовместимых типов делегатов. Все делегаты будут хорошо играть вместе, потому что они просто будут сопоставлены на основе их параметров / типов возврата.

Это не решает все проблемы, потому что FuncAction) не может иметь outили refпараметры, но они используются реже.

Обновление: в комментариях Свиш говорит:

Тем не менее, переключение типа параметра с Func на Predicate и обратно, похоже, не имеет значения? По крайней мере, он все еще компилируется без проблем.

Да, если ваша программа только назначает методы для делегатов, как в первой строке моей Mainфункции. Компилятор автоматически генерирует код для нового объекта делегата, который пересылается в метод. Таким образом, в моей Mainфункции я мог изменить x1тип, ExceptionHandler2не вызывая проблем.

Однако во второй строке я пытаюсь назначить первый делегат другому делегату. Даже если предположить, что 2-й тип делегата имеет одинаковые параметры и возвращаемые типы, компилятор выдает ошибку CS0029: Cannot implicitly convert type 'ExceptionHandler1' to 'ExceptionHandler2'.

Может быть, это прояснит это:

public static bool IsNegative(int x)
{
    return x < 0;
}

static void Main(string[] args)
{
    Predicate<int> p = IsNegative;
    Func<int, bool> f = IsNegative;

    p = f; // Not allowed
}

Мой метод IsNegativeявляется вполне хорошей вещью , чтобы назначить на pи fпеременные, до тех пор , как я делаю это непосредственно. Но тогда я не могу присвоить одну из этих переменных другой.

Дэниел Уорвикер
источник
Тем не менее, переключение типа параметра с Func <T, bool> на Predicate <T> и обратно, похоже, не имеет значения? По крайней мере, он все еще компилируется без проблем.
Свиш,
Кажется, MS пытается отговорить разработчиков думать, что это одно и то же. Интересно, почему?
Мэтт Коджан
1
Переключение типа параметра имеет значение , если выражение , которое вы передаете ему было определено отдельно для вызова метода, так как тогда он будет классифицирован как либо Func<T, bool>или Predicate<T>вместо того , тип , выведенный компилятором.
Адам Ральф
30

Совет (в 3.5 и выше) заключается в использовании Action<...>и Func<...>- для «почему?» - одним из преимуществ является то, что « Predicate<T>» имеет смысл только в том случае, если вы знаете, что означает «предикат» - в противном случае вам нужно взглянуть на объект-браузер (и т. д.), чтобы найти подпись.

Обратно Func<T,bool>следует стандартному образцу; Я могу сразу сказать, что это функция, которая принимает Tи возвращает bool- не нужно понимать никакой терминологии - просто примените мой тест на истинность.

Для «предиката» это могло бы быть нормально, но я ценю попытку стандартизировать. Это также позволяет добиться большого паритета со связанными методами в этой области.

Марк Гравелл
источник