Чому Func <T, bool> замість предиката <T>?


210

Це лише питання цікавості, мені було цікаво, чи хтось добре відповів:

У бібліотеці .NET Framework Class є, наприклад, такі два методи:

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методи і методи розширення ... Що з цим робити ?


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<>не спеціальні делегати та предикати


7
Класно, ніколи раніше не бачив цих керівних принципів =)
Свиш

6
Прийме це як відповідь, оскільки в ньому є більшість голосів. і тому, що у Джона Скіта є багато представників ...: p
Свиш

4
Це химерні настанови, як написано. Звичайно, в ньому повинно бути зазначено "Чи використовувати нові типи LINQ" Func <> "та" Action <> "[...]". Вираз <> - зовсім інша річ.
Джон Скіт

3
Ну, насправді ви повинні розмістити це в контексті настанови, яка стосується написання постачальника LINQ. Отже, так, для операторів LINQ керівництво полягає у використанні функцій <> та виразу <> як параметрів для методів розширення. Я погоджуюся, що я буду вдячний окремим керівництвом щодо використання функцій та дій
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 інформатики), передбачається на одному значенні. Це може бути предикат <Пара <T, int >> звичайно, але це ще потворніше :)
Джон Скіт

так правда ... хе-хе. Ні, я думаю, що Func чистіший.
Свиш

2
Іншу відповідь прийняли через вказівки. Хочеться, я міг би відзначити більше, ніж одну відповідь! Було б добре, якби я міг позначити ряд відповідей як "Дуже примітний" або "Також маючи хороший пункт, який слід зазначити на додаток до відповіді"
Свиш

6
Гм, щойно побачив, що я написав перший коментар неправильно: P моя пропозиція, звичайно, буде предикатом <T, int> ...
Svish

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не викликаючи проблем.

Однак у другому рядку я намагаюся призначити першого делегата іншому делегату. Навіть подумавши, що другий тип делегата має точно такий же параметр і типи повернення, компілятор дає помилку 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- не потрібно розуміти жодної термінології - просто застосуйте мій тест на істинність.

Для "предиката" це могло бути нормально, але я ціную спробу стандартизації. Це також дозволяє багато співпадати з відповідними методами в цій галузі.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.