Func vs. Action vs. Predicate

С реальными примерами и их использованием, может кто-нибудь помочь мне понять:

  1. Когда нам нужен _1 _ делегировать?
  2. Когда нам нужен _2 _ делегировать?
  3. Когда нам нужен _3 _ делегировать?

person InfoLearner    schedule 30.11.2010    source источник
comment
Несмотря на то, что это дубликат, у него есть более подробный принятый ответ   -  person reggaeguitar    schedule 14.03.2016
comment
@reggaeguitar Что интересно, оба принятых ответа от Джона Скита   -  person denvercoder9    schedule 03.12.2016
comment
Чтение книги Джона Скита «Глубоко C #» привело меня сюда. Поговорим о начале.   -  person Matthew Peterson    schedule 24.02.2020


Ответы (3)


Разница между Func и Action заключается просто в том, хотите ли вы, чтобы делегат возвращал значение (используйте Func) или нет (используйте Action).

Func, вероятно, наиболее часто используется в LINQ - например, в проекциях:

 list.Select(x => x.SomeProperty)

или фильтрация:

 list.Where(x => x.SomeValue == someOtherValue)

или выбор ключа:

 list.Join(otherList, x => x.FirstKey, y => y.SecondKey, ...)

Action чаще используется для таких вещей, как List<T>.ForEach: выполнить заданное действие для каждого элемента в списке. Я использую его реже, чем Func, хотя я действительно иногда использую версию без параметров для таких вещей, как Control.BeginInvoke и Dispatcher.BeginInvoke.

Predicate - это просто специальный корпус Func<T, bool>, введенный до того, как пришли все Func и большинство Action делегатов. Я подозреваю, что если бы у нас уже были Func и Action в их различных обличьях, Predicate не был бы представлен ... хотя он действительно придает определенный смысл использованию делегата, тогда как Func и Action используются в самых разных целях.

Predicate в основном используется в List<T> для таких методов, как FindAll и RemoveAll.

person Jon Skeet    schedule 30.11.2010
comment
Мне нравится видеть Predicate в сигнатуре функции. Это показывает, что переданный метод принимает решение, а не просто возвращает код успеха или другой вид bool. - person Ron Warholic; 30.11.2010
comment
Есть ли какие-нибудь рекомендации по дизайну, выбирать между Predicate<T> или Func<T,bool>? Мне нравится выразительность Predicate, но я видел рекомендации и для Func. - person CodesInChaos; 30.11.2010
comment
@Ron: Да, именно об этом и был мой предпоследний абзац. - person Jon Skeet; 30.11.2010
comment
@CodeInChaos: Не то, чтобы я видел. LINQ использует Func повсюду, но это, вероятно, для согласованности, учитывая, что многие методы, принимающие Func<T,bool> в качестве предиката, также имеют перегрузку, принимающую Func<T,int,bool> в качестве индексированного предиката. - person Jon Skeet; 30.11.2010
comment
Мне нравится, как просто вы это объяснили. func - возвращает действие - выполняет (метод) - person Juvil; 10.01.2013
comment
Кто подумал, что было бы полезно иметь 3 разных названия для одного и того же? Это просто указатель на функцию! - person gimbar; 03.12.2013
comment
@JonSkeet, Да, но ваш предпоследний абзац читается как история того, как что-то пошло не так, скрывая это намерение в середине и делая его похожим на комментарий. Комментарий @Ron ясен тем, что это желаемый эффект (а не просто побочный продукт эволюции программного обеспечения), и указывает на то, что может быть сделан намеренный выбор использовать Predicates в некоторых случаях использования и Func<…,bool>s в других для передачи смысла. По совпадению, можно сказать, что разница между вашим предпоследним абзацем и комментарием @Ron подобна разнице между использованием Func<T,bool> и Predicate<T>. - person Slipp D. Thompson; 28.01.2015
comment
Как бы то ни было, в C ++ понятие предиката также означает, что функция предполагается чистой функцией, то есть она всегда должна давать один и тот же результат, если ей вводится один и тот же ввод, в то время как простые функции, производящие логические значения, не дают такого гарантия. Конечно, это не выполняется компилятором или чем-то еще, но явное указание, что параметр функции является предикатом, помогает документировать такого рода предположения (что может быть довольно важным в некоторых контекстах, например, для многопоточности). - person ZeRemz; 21.04.2015
comment
Обратите внимание, что Linq (или разрешение перегрузки как C #, так и VB) позволит Func<T,bool> там, где ему нужен Predicate<T> , но не наоборот: не может преобразовать из System.Predicate ‹int› в System. Func ‹int, bool› '. - person Mark Hurd; 26.07.2015
comment
Это означает, что если вы создаете метод расширения Where<T>(this s, Predicate p) для нескольких случаев, когда у вас уже есть Predicate для использования в предложении Where, внезапно все ваши предложения Where будут соответствовать этому новому методу расширения (включая тот, который сейчас рекурсивно используется внутри, ожидая, что он продолжит для ссылки на существующий метод расширения Where :-(). - person Mark Hurd; 26.07.2015
comment
@MarkHurd: мне нужно было увидеть точную ситуацию, чтобы понять, почему разрешение перегрузки ведет себя таким образом. Обратите внимание, что существует большая разница между лямбда-выражением, которое может быть преобразовано либо в Func<T, bool>, либо в Predicate<T>, и ссылкой на существующий Predicate<T>. Чтобы вызвать исходный метод расширения, просто вызовите его как статический метод: Enumerable.Where(...) - person Jon Skeet; 26.07.2015
comment
Часть того, что я заметил, относится к VB.NET: насколько я могу судить, в C # нет эквивалента для Dim IsEven = Function(x As Integer) ((x And 1) = 0). Эквивалент var IsEven = (int x) => ((x & 1) == 0); жалуется: невозможно присвоить лямбда-выражение неявно типизированной локальной переменной. - person Mark Hurd; 26.07.2015
comment
@MarkHurd: Похоже, VB создает новый тип делегата для подобных ситуаций. - person Jon Skeet; 26.07.2015
comment
И это означает, что queryable.Where(IsEven) только IEnumerable использует .Where расширения, предоставленные Linq, но когда я предоставляю свои собственные Predicate расширения (одно для IEnumerable и одно для IQueryable), это IQueryable. - person Mark Hurd; 26.07.2015

Действие - это делегат (указатель) на метод, который принимает ноль, один или несколько входных параметров, но ничего не возвращает.

Func - это делегат (указатель) на метод, который принимает ноль, один или несколько входных параметров и возвращает значение (или ссылку).

Предикат - это особый вид Func, который часто используется для сравнений.

Хотя Action и Func широко используются с Linq, они логически независимы от Linq. C ++ уже содержал базовую концепцию в виде указателей на типизированные функции.

Вот небольшой пример для Action и Func без использования Linq:

class Program
{
    static void Main(string[] args)
    {
        Action<int> myAction = new Action<int>(DoSomething);
        myAction(123);           // Prints out "123"
                                 // can be also called as myAction.Invoke(123);

        Func<int, double> myFunc = new Func<int, double>(CalculateSomething);
        Console.WriteLine(myFunc(5));   // Prints out "2.5"
    }

    static void DoSomething(int i)
    {
        Console.WriteLine(i);
    }

    static double CalculateSomething(int i)
    {
        return (double)i/2;
    }
}
person Knasterbax    schedule 02.10.2012
comment
Предикат - это делегат, который принимает общие параметры и возвращает логическое значение. - person Martin; 08.11.2013
comment
Хороший пример! Обратите внимание, что вы также можете вызвать myAction, просто вызвав его: myAction(123);. Вам не нужно использовать .Invoke() - person romar; 22.12.2013
comment
и просто для полноты, если вы хотите использовать Func, который не принимает параметров, но возвращает значение (или ссылку), вы должны написать что-то вроде этого Func ‹double› myFoo = new Func ‹double› (foo); и Определение foo может быть static double foo () {return 1.0;} - person A.B.; 07.04.2018
comment
@Martin Predicate принимает только один параметр. - person MarredCheese; 19.03.2019

Func - если вам нужен делегат для функции, которая может принимать или не принимать параметры и возвращать значение. Наиболее распространенный пример - Выбрать из LINQ:

var result = someCollection.Select( x => new { x.Name, x.Address });

Действие - если вам нужен делегат для функции, которая может принимать или не принимать параметры и не возвращать значение. Я часто использую их для анонимных обработчиков событий:

button1.Click += (sender, e) => { /* Do Some Work */ }

Предикат - если вам нужна специализированная версия Func, которая оценивает значение по набору критериев и возвращает логический результат (true для совпадения, false в противном случае). Опять же, они довольно часто используются в LINQ для таких вещей, как Where:

var filteredResults = 
    someCollection.Where(x => x.someCriteriaHolder == someCriteria);

Я только что дважды проверил, и оказалось, что LINQ не использует предикаты. Не знаю, почему они приняли это решение ... но теоретически это все еще ситуация, в которой предикат подойдет.

person Justin Niessner    schedule 30.11.2010
comment
Я знаю, что это старый, но Predicate не использовался LINQ, потому что он предшествует Func и Action, которые мы знаем сегодня, а последние два могут использоваться для достижения того же результата гораздо лучше. - person gparent; 07.01.2012
comment
@gparent, почему лучше? Я считаю, что удобочитаемость лучше, если вместо Func или Action используются делегаты с осмысленными именами. - person Sam; 14.06.2013
comment
Это зависит от того, как вы используете делегатов, но иногда имя не так важно, и необходимость создавать функции и переменные только для одного использования снижает удобочитаемость. - person gparent; 14.06.2013