JavaRush /Курсы /C# SELF /Комбинирование лямбда-выражений и делегатов

Комбинирование лямбда-выражений и делегатов

C# SELF
50 уровень , 4 лекция
Открыта

1. Введение

Делегат — это тип, который описывает “подпись” функции: какие параметры принимает, что возвращает. Он как паспорт для функции или пропуск в ночной клуб для кода: если подпись совпадает — проходи, не совпадает — извини, тебя не пустят.

Пример:

public delegate int Operation(int a, int b);

Такой делегат обозначает функцию, принимающую два int и возвращающую int. Вот так выглядело программирование “до лямбд” (свежо, но не очень удобно):

Operation add = delegate (int x, int y)
{
    return x + y;
};

С приходом лямбд код стал выглядеть лаконичнее:

Operation add = (x, y) => x + y;

Интересный факт: В C# лямбда-выражение — это просто способ создать “анонимную функцию”, которая автоматически компилируется в экземпляр делегата подходящего типа. А вот так связь между ними становится практически неразличимой!

Встроенные делегаты: Func, Action, Predicate

Чтобы не плодить бесконечно много собственных делегатов для каждого случая, в C# есть “универсальные” типы-функций:

  • Func<T1, T2, ..., TResult> — универсальный делегат, принимающий параметры (T1, T2, ...) и возвращающий значение типа TResult.
  • Action<T1, T2, ...> — почти то же самое, но ничего не возвращает (void).
  • Predicate<T> — универсальный делегат, принимающий один параметр и возвращающий bool (любимец всех “фильтров”).
Делегат Возвращаемый тип Пример синтаксиса
Func<int, int>
int
Func<int, int> f = x => x + 1;
Action<string>
void
Action<string> a = s => ...;
Predicate<double>
bool
Predicate<double> p = d => ...;

Пример:

Func<int, int, int> add = (a, b) => a + b;
Action<string> show = s => Console.WriteLine(s);
Predicate<int> isEven = n => n % 2 == 0;

2. Лямбда-выражения “в конверте” делегата

Передача лямбды методу с делегатом

Любой метод, который принимает делегат, может “съесть” и лямбду, если её подпись совпадает с ожидаемой.

public static void CalculateAndShow(int a, int b, Func<int, int, int> operation)
{
    int result = operation(a, b);
    Console.WriteLine($"Результат: {result}");
}

// Вызываем с лямбдой
CalculateAndShow(5, 7, (x, y) => x * y); // Результат: 35

Пример с Action

void ProcessString(string message, Action<string> processor)
{
    processor(message);
}

// Используем лямбду
ProcessString("Привет, мир!", s => Console.WriteLine(s.ToUpper()));

Пример с Predicate

List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6 };
List<int> evenNumbers = numbers.FindAll(n => n % 2 == 0);

Вот в такие моменты и понимаешь: зачем выдумывать самописные делегаты, если есть проверенное "колесо" от Microsoft.

Как работают делегаты и лямбды вместе

flowchart TD
    A[Лямбда-выражение] -->|Создаёт| B((Делегат))
    B -->|Передаётся как аргумент| C[Метод]
    B -->|Хранится| D[Свойство/Поле/Коллекция]
    C -->|Вызывает| E[Код лямбды]

3. Передача лямбды как делегата в собственных методах

Если ваш собственный метод использует делегаты, лямбда делает использование максимально гибким.

Пример: наш мини-калькулятор

// Определяем метод для работы с операциями
static int PerformOperation(int a, int b, Func<int, int, int> operation)
{
    return operation(a, b);
}

// Применяем разные операции (лямбды - в деле!)
int sum = PerformOperation(3, 4, (x, y) => x + y);
int multiply = PerformOperation(3, 4, (x, y) => x * y);
int max = PerformOperation(3, 4, (x, y) => x > y ? x : y);

Console.WriteLine(sum);      // 7
Console.WriteLine(multiply); // 12
Console.WriteLine(max);      // 4

Видите, как элегантно меняется логика, вместо трёх отдельных методов?

4. Лямбды + делегаты в LINQ (и не только)

В LINQ методы ожидают “логическую мини-функцию”, то есть как раз делегат. Почти все стандартные LINQ-методы принимают такие типы:

  • Func<T, bool> для фильтрации (Where)
  • Func<T, TResult> для трансформаций (Select)
  • Func<T, TKey> для сортировок (OrderBy)
List<string> names = new List<string> { "Анна", "Олег", "Виктор", "Яна" };

// Получаем только имена длиннее 3-х символов
var longNames = names.Where(name => name.Length > 3);

// Преобразуем имена в верхний регистр
var upperNames = names.Select(name => name.ToUpper());

LINQ — это “лямбды на максималках”, потому что в них всё, что можно передать как делегат, можно передать как лямбду!

5. Комбинирование: хранение и передача делегатов-лямбд

Список функций

Лямбда — это не просто временный анонимный метод. Вы можете хранить набор лямбд в коллекции и использовать их динамически.

List<Func<int, int, int>> operations = new List<Func<int, int, int>>
{
    (x, y) => x + y,
    (x, y) => x - y,
    (x, y) => x * y,
    (x, y) => x / y
};

foreach (var op in operations)
{
    Console.WriteLine(op(10, 2));
}

Такой приём часто встречается в тестах, обработчиках и плагинах.

Словарь делегатов

А теперь, если вы фанат plug-and-play архитектуры:

var mathFuncs = new Dictionary<string, Func<int, int, int>>
{
    { "plus", (x, y) => x + y },
    { "minus", (x, y) => x - y },
    { "pow", (x, y) => (int)Math.Pow(x, y) }
};

string command = "pow"; // Имитация пользовательского ввода

if (mathFuncs.TryGetValue(command, out var operation))
{
    Console.WriteLine(operation(2, 5)); // 32
}
else
{
    Console.WriteLine("Команда не найдена");
}

6. Полезные нюансы

Лямбда-делегаты: возврат делегата из метода

Можно возвращать делегаты (а значит, и лямбды) из методов — получается почти фабрика функций!

Func<int, int> GetMultiplier(int factor)
{
    // Возвращаем лямбда-делегат, который будет умножать на factor
    return x => x * factor;
}

var triple = GetMultiplier(3);
Console.WriteLine(triple(5)); // 15

var quadruple = GetMultiplier(4);
Console.WriteLine(quadruple(5)); // 20

Вот так замыкание (closure) становится не игрушкой, а реальным инструментом!

Составление функций: Combine, Delegate.Combine и мультиделегаты

Иногда хочется, чтобы один “вызов” привёл к выполнению сразу нескольких функций. Например, обработка событий в GUI: жмём на кнопку — и сразу несколько обработчиков реагируют.

Action и делегаты позволяют “состыковывать” несколько функций:

Action<string> pipeline = s => Console.WriteLine("Шаг 1: " + s);
pipeline += s => Console.WriteLine("Шаг 2: " + s.ToUpper());
pipeline += s => Console.WriteLine("Шаг 3: " + s.Length);

pipeline("тест");
// Выводит:
// Шаг 1: тест
// Шаг 2: ТЕСТ
// Шаг 3: 4

Удобно для сценариев “цепочек” или обработки событий.

Важно: Для функций, возвращающих значения, результат будет только от последнего вызова (“хвост функции”). Для Action — выполняются все, так как void.

Использование лямбда-делегатов как параметры callback

Классический сценарий — “вызови меня, когда закончишь”.

void DownloadFile(string url, Action<string> onFinish)
{
    // Имитация загрузки файла...
    System.Threading.Thread.Sleep(500);// (Имитация долгой работы, не используйте в реальных GUI-приложениях)
    onFinish($"Загрузка '{url}' завершена!");
}

DownloadFile("http://example.com", msg => Console.WriteLine(msg));

В реальных приложениях (например, при работе с API, базами, файлами) — это стандартный паттерн.

Функция высшего порядка: пример фильтра по предикату

Лямбда-выражения и делегаты позволяют писать универсальные функции, которые принимают другие функции как параметры. Это основа шаблона “функции высшего порядка”.

List<int> Filter(List<int> source, Predicate<int> condition)
{
    List<int> result = new List<int>();
    foreach (var item in source)
        if (condition(item)) result.Add(item);
    return result;
}

// Используем разные лямбды:
var onlyPositive = Filter(new List<int> { -2, 0, 2, 7 }, x => x > 0);
var onlyEven = Filter(new List<int> { -2, 0, 2, 7 }, x => x % 2 == 0);

Можно даже создавать “фабрики” лямбд:

Func<int, Predicate<int>> GetRangeChecker(int min, int max) =>
    x => x >= min && x <= max;

Predicate<int> inRange = GetRangeChecker(1, 10);

var filtered = Filter(new List<int> { 0, 5, 10, 15 }, inRange); // 5, 10

7. Типичные ошибки и нюансы комбинирования

Если подпись параметров не совпадает, компилятор будет бурчать (CS1660: "Cannot convert lambda expression").

Лямбда-выражение может неожиданно “запоминать” переменные, которые позже меняются (замыкание / closure) — будьте внимательны к захвату переменных.

Мультиделегаты с возвращаемым значением возвращают результат только от последнего обработчика — часто неожиданность для начинающих.

В местах, где нужны выражения Expression<Func<int,bool>>, нельзя подставить statement-лямбду — требуется именно expression-лямбда.

2
Задача
C# SELF, 50 уровень, 4 лекция
Недоступна
Использование Func и Action
Использование Func и Action
1
Опрос
Делегаты - тип для лямбда-выражений, 50 уровень, 4 лекция
Недоступен
Делегаты - тип для лямбда-выражений
Взаимосвязь лямбда-выражений и делегатов
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ