Почему Func <T, bool> вместо Predicate <T>? - PullRequest
205 голосов
/ 20 марта 2009

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

В библиотеке классов .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 и методами расширения ... что с этим?

Ответы [ 4 ]

158 голосов
/ 20 марта 2009

Хотя 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<> вместо пользовательских делегаты и предикаты

110 голосов
/ 20 марта 2009

Я задавался этим вопросом раньше. Мне нравится делегат 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>)

не будет.

30 голосов
/ 20 марта 2009

Конечно, настоящая причина использования 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, но они используются реже.

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

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

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

27 голосов
/ 20 марта 2009

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

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

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

...