Лямбда-выражения
В прошлом уроке каждый метод, который мы передавали, сначала объявляли отдельно и давали ему имя: Twice, Square, IsEven. Для вычисления в одну строку, которое нужно в одном месте программы, это лишняя работа.
С карточкой рецепта то же самое. Чтобы отдать другу короткий рецепт, его не обязательно сначала вписывать в кулинарную книгу и давать ему название. Его можно написать прямо на карточке, в ту минуту, когда вы её отдаёте.
В C# такую карточку пишут прямо там, где нужен делегат:
Func<int, int> twice = x => x * 2;
Console.WriteLine(twice(5)); // 10
List<int> nums = [4, -1, 6, -3];
nums.RemoveAll(n => n < 0);
Console.WriteLine(string.Join(" ", nums)); // 4 6Давайте разберём программу по строкам:
x => x * 2— метод без имени: принимаетxи возвращаетx * 2. Он сразу попадает в переменнуюtwice.twice(5)вызывает его, как любой делегат, и печатает10.nums.RemoveAll(n => n < 0)получает проверку прямо в аргументе. Метод списка вызывает её для каждого элемента и удаляет те, для которых она вернулаtrue.- В списке остаются
4и6.
Как устроена лямбда
Конструкцию x => x * 2 называют лямбда-выражением, или лямбдой: это метод без имени, который пишут прямо там, где он нужен. Она состоит из трёх частей:
xслева от=>— параметр;=>— та же стрелка, что в коротких методах вродеint Twice(int x) => x * 2;;x * 2справа — тело: его значение лямбда возвращает.
Типа параметра в лямбде не видно. Компилятор берёт его из делегата, в который лямбда попадает. Func<int, int> принимает int, значит, и x — это int. А RemoveAll у списка List<int> ждёт Predicate<int>, поэтому n тоже int.
Этот вопрос часто задают на собеседовании: что такое лямбда-выражение? Ответ: метод без имени, который записывают прямо в выражении и присваивают делегату или передают в метод.
Формы лямбды
Лямбда умеет то же, что метод: принимать несколько параметров или ни одного, выполнять несколько инструкций. Для каждого случая своя форма:
Func<int, int, int> add = (a, b) => a + b;
Func<int> answer = () => 42;
Func<int, int> next = x =>
{
int y = x + 1;
return y * 10;
};
Action<string> greet = name => Console.WriteLine($"Hi, {name}!");
Console.WriteLine(add(2, 3)); // 5
Console.WriteLine(answer()); // 42
Console.WriteLine(next(4)); // 50
greet("Ann"); // Hi, Ann!Один параметр
Скобки вокруг единственного параметра не нужны: x => x * 2. Это самая частая форма.
Несколько параметров или ни одного
Параметры перечисляют в скобках через запятую: (a, b) => a + b. Лямбда без параметров начинается с пустых скобок: () => 42.
Тело из нескольких инструкций
Если одной строки мало, тело берут в фигурные скобки, как у метода. Значение тогда возвращает return: лямбда next сначала вычисляет y, а потом возвращает y * 10. Лямбда для Action ничего не возвращает, поэтому return в ней не нужен.
Явные типы параметров
Тип параметра можно указать и самому: (int x) => x * 2. Тогда скобки нужны даже вокруг одного параметра. Чаще тип не пишут и оставляют его делегату.
Карточка без книги
Здесь аналогия с карточкой заканчивается. Рецепт на карточке понятен и сам по себе, а лямбде нужен делегат. Без него компилятор не знает ни тип параметра, ни тип результата: лямбда x => x * 2 подходит и для int, и для double.
Частая ошибка
Лямбду записали в переменную, объявленную через var:
var twice = x => x * 2; // ✗
Console.WriteLine(twice(5));error CS8917: The delegate type could not be inferred.Программа не компилируется. Справа от = нет ни одного типа, и компилятору неоткуда узнать, чем будет x. Исправить можно двумя способами:
- Назвать делегат явно:
Func<int, int> twice = x => x * 2;. - Указать тип параметра:
var twice = (int x) => x * 2;. Тогда компилятор сам выберет для переменной типFunc<int, int>.
Главное
- Лямбда — метод без имени, записанный прямо там, где он нужен:
x => x * 2. - Слева от
=>параметры, справа тело; несколько параметров или ни одного берут в скобки, несколько инструкций — в фигурные скобки сreturn. - Типы параметров и результата лямбда получает от делегата, поэтому
var twice = x => x * 2;не компилируется.