Чем отличаются Action, Func и Predicate? Когда какой использовать?
Начал писать на C# и постоянно вижу в чужом коде Action, Func<> и Predicate<> вместо объявления своих делегатов через delegate. Понял, что это какие-то встроенные делегаты, но не врубаюсь, в чём между ними разница и когда что брать.
Вот, например, такой кусок — почему тут Func, а не Action?
List<int> nums = new() { 1, 2, 3, 4, 5 };
Func<int, bool> isEven = x => x % 2 == 0;
var evens = nums.Where(isEven);
А в другом месте видел Action<string>. Можно по-человечески объяснить, чем они отличаются и почему вообще три разных типа?
2 ответа
Это три готовых обобщённых делегата, чтобы не объявлять свои каждый раз. Разница только в том, что они возвращают:
Action— метод, который ничего не возвращает (void).Action<int, string>— принимает int и string, возвращает void.Func— метод, который что-то возвращает. Последний параметр типа — это тип возврата.Func<int, bool>— принимает int, возвращает bool.Predicate<T>— частный случай: принимаетT, возвращаетbool. По сути то же, чтоFunc<T, bool>, просто читается как «условие/проверка».
Action<string> log = msg => Console.WriteLine(msg); // void
Func<int, int, int> sum = (a, b) => a + b; // возвращает int
Predicate<int> isPositive = x => x > 0; // возвращает bool
log("привет");
Console.WriteLine(sum(2, 3)); // 5
Console.WriteLine(isPositive(-4)); // False
Правило простое: возвращаешь значение → Func, не возвращаешь → Action, проверка на условие → Predicate (или Func<T,bool>). В LINQ почти везде Func, поэтому ты его и видишь.
Маленькое практическое уточнение: Predicate<T> в современных API почти не используют — методы LINQ (Where, Any, Count) принимают Func<T, bool>. Predicate<T> остался в основном в старых методах List<T>, например Find и RemoveAll:
var list = new List<int> { 1, 2, 3, 4 };
list.RemoveAll(x => x % 2 == 0); // тут параметр — Predicate<int>
Так что на практике чаще всего хватает Action и Func, а Predicate встречается реже.