Перейти к содержимому
Теория · Лямбды и LINQ · урок 2 из 10

Лямбда-выражения

В прошлом уроке каждый метод, который мы передавали, сначала объявляли отдельно и давали ему имя: Twice, Square, IsEven. Для вычисления в одну строку, которое нужно в одном месте программы, это лишняя работа.

С карточкой рецепта то же самое. Чтобы отдать другу короткий рецепт, его не обязательно сначала вписывать в кулинарную книгу и давать ему название. Его можно написать прямо на карточке, в ту минуту, когда вы её отдаёте.

В C# такую карточку пишут прямо там, где нужен делегат:

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

Давайте разберём программу по строкам:

  1. x => x * 2 — метод без имени: принимает x и возвращает x * 2. Он сразу попадает в переменную twice.
  2. twice(5) вызывает его, как любой делегат, и печатает 10.
  3. nums.RemoveAll(n => n < 0) получает проверку прямо в аргументе. Метод списка вызывает её для каждого элемента и удаляет те, для которых она вернула true.
  4. В списке остаются 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.

Этот вопрос часто задают на собеседовании: что такое лямбда-выражение? Ответ: метод без имени, который записывают прямо в выражении и присваивают делегату или передают в метод.

Формы лямбды

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

C#
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:

C#
var twice = x => x * 2; // ✗
Console.WriteLine(twice(5));
TEXT
error CS8917: The delegate type could not be inferred.

Программа не компилируется. Справа от = нет ни одного типа, и компилятору неоткуда узнать, чем будет x. Исправить можно двумя способами:

  1. Назвать делегат явно: Func<int, int> twice = x => x * 2;.
  2. Указать тип параметра: var twice = (int x) => x * 2;. Тогда компилятор сам выберет для переменной тип Func<int, int>.

Главное

  • Лямбда — метод без имени, записанный прямо там, где он нужен: x => x * 2.
  • Слева от => параметры, справа тело; несколько параметров или ни одного берут в скобки, несколько инструкций — в фигурные скобки с return.
  • Типы параметров и результата лямбда получает от делегата, поэтому var twice = x => x * 2; не компилируется.