Avatar ·

В чем разница между Func, Action и Predicate в C#? Когда что использовать?

Привет, коллеги! Я недавно начал изучать C# и наткнулся на встроенные делегаты: Func, Action и Predicate. Вроде бы они все похожи — это какие-то обобщенные делегаты, но я путаюсь, когда какой использовать.

Я понимаю, что все они позволяют передавать методы как параметры, но сигнатуры у них разные. Например, я видел такой код:

Func<int, int, int> add = (x, y) => x + y;
Action<string> print = s => Console.WriteLine(s);
Predicate<int> isEven = x => x % 2 == 0;

Вроде бы все три принимают аргументы и что-то делают. Но почему для проверки четности используется Predicate, а не Func<int, bool>? И зачем вообще нужен Action, если можно использовать Func с возвращаемым типом void?

Подскажите, пожалуйста, в чем принципиальная разница между этими тремя типами делегатов, и как выбрать правильный в конкретной ситуации? Особенно интересует, когда использовать Predicate, а когда — Func с булевым возвратом. Заранее спасибо!

<<>>

Краткий ответ: разница в сигнатуре и семантике

Func, Action и Predicate — это встроенные обобщенные делегаты, которые отличаются наличием возвращаемого значения и количеством параметров. Вот их ключевые отличия:

  • Action — не возвращает значение (void). Может принимать от 0 до 16 параметров.
  • Func — всегда возвращает значение. Последний тип в списке параметров — это тип возвращаемого значения. Может принимать от 0 до 16 входных параметров.
  • Predicate — всегда возвращает bool и принимает ровно один параметр. Это частный случай Func<T, bool>.

Подробное объяснение с примерами

1. Action — для действий без возврата

Используйте Action, когда нужно выполнить какое-то действие (метод), но не нужно получать результат. Например, логирование, вывод в консоль, обновление UI.

// Action без параметров
Action sayHello = () => Console.WriteLine("Hello!");
sayHello(); // Вывод: Hello!

// Action с параметрами
Action<string, int> logMessage = (msg, level) =>
{
    Console.WriteLine($"[{level}] {msg}");
};
logMessage("Старт приложения", 1); // Вывод: [1] Старт приложения

2. Func — для преобразований с результатом

Используйте Func, когда метод должен вернуть какое-то значение. Последний параметр в угловых скобках — это тип возвращаемого значения.

// Func без параметров, возвращает int
Func<int> getRandomNumber = () => new Random().Next(1, 100);
int number = getRandomNumber(); 

// Func с двумя параметрами (int, int) и возвратом int
Func<int, int, int> multiply = (a, b) => a * b;
int result = multiply(5, 3); // result = 15

// Func с параметром string и возвратом bool
Func<string, bool> isLongerThan5 = s => s.Length > 5;
bool check = isLongerThan5("Hello!"); // false

3. Predicate — для проверки условий

Predicate<T> — это специализированный делегат, который принимает один параметр типа T и возвращает bool. Он семантически означает "проверка условия" или "предикат". Чаще всего используется в LINQ-методах, таких как Where, Find, Exists.

Predicate<int> isEven = x => x % 2 == 0;

// Использование с List.Find
List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
int firstEven = numbers.Find(isEven); // 2

// Использование с List.Exists
bool hasEven = numbers.Exists(isEven); // true

// Альтернатива с Func (работает так же, но менее читаемо)
Func<int, bool> isEvenFunc = x => x % 2 == 0;
bool hasEven2 = numbers.Any(isEvenFunc); // тоже true

Почему Predicate, а не Func<T, bool>?

Исторически Predicate появился раньше (в .NET 2.0), до появления Func и Action (в .NET 3.5). Сейчас Predicate — это просто удобный псевдоним для Func<T, bool>. Используйте его, когда:

  • Работаете с методами, которые принимают именно Predicate (например, List.Find, List.TrueForAll).
  • Хотите явно подчеркнуть в коде, что это проверка условия, а не просто функция, возвращающая bool.

Важно: В современном LINQ предпочитают Func<T, bool> (методы Where, Any, All принимают именно Func).

Практические рекомендации

  • Нужно выполнить действие без возврата → Action
  • Нужно преобразовать данные и вернуть результат → Func
  • Нужно проверить один элемент на условие → Predicate (или Func<T, bool>)
<<>>

Дополнение: когда использовать Action вместо void-метода

Первый ответ отлично объясняет различия. Я добавлю про практическое применение Action и Func в реальном коде.

Пример с Action для обратного вызова

Представьте, что вы пишете метод, который выполняет длительную операцию и хочет уведомить вызывающий код о прогрессе:

public void ProcessLargeData(List<int> data, Action<int> onProgress)
{
    for (int i = 0; i < data.Count; i++)
    {
        // ... обработка данных ...
        onProgress?.Invoke(i); // вызываем callback
    }
}

// Использование:
ProcessLargeData(myData, percent => Console.WriteLine($"Готово: {percent}%"));

Пример с Func для стратегии

Используйте Func, когда нужно передать алгоритм (стратегию) в метод:

public int CalculateDiscount(int price, Func<int, int> discountStrategy)
{
    return discountStrategy(price);
}

// Разные стратегии скидок:
int tenPercent = CalculateDiscount(1000, p => p * 0.9);
int fixedDiscount = CalculateDiscount(1000, p => p - 100);

Совет: Не бойтесь использовать Func вместо Predicate в новом коде — это более универсально. Но если вы видите в чужом коде Predicate, знайте, что это то же самое, что Func<T, bool>, просто с более явным названием.