Как лямбда-выражение связано с делегатом в C#?

Ответ

Лямбда-выражение — это компактный синтаксис для создания анонимного метода, который может быть неявно преобразован в экземпляр делегата или в дерево выражений (Expression<T>). Компилятор C# автоматически генерирует код делегата на основе лямбды.

Практический пример:

// Лямбда-выражение присваивается делегату типа Func<int, int, int>
Func<int, int, int> add = (x, y) => x + y;
int result = add(5, 3); // result = 8

// Та же лямбда может быть использована для другого совместимого делегата
Action<int, int> printSum = (x, y) => Console.WriteLine(x + y);
printSum(5, 3); // Вывод: 8

Ключевые моменты связи:

  1. Автоматическое преобразование: Компилятор выводит тип делегата из контекста. Сигнатура лямбды (типы параметров и возвращаемого значения) должна соответствовать сигнатуре делегата.
  2. Замыкания: Лямбда может захватывать ("видеть") локальные переменные и параметры метода, в котором она объявлена. Для этого компилятор создает скрытый класс-замыкание.
    int multiplier = 2;
    Func<int, int> multiplierFunc = x => x * multiplier; // Захват переменной multiplier
  3. Деревья выражений: Если лямбда присваивается типу Expression<TDelegate>, компилятор строит дерево выражений (данные о коде), а не исполняемый IL-код. Это основа таких технологий, как LINQ to SQL.
    // Дерево выражений, а не делегат
    Expression<Func<int, int, int>> expression = (x, y) => x + y;

    Использование лямбд вместо явного создания делегатов через new DelegateType() делает код значительно чище и читабельнее.

Ответ 18+ 🔞

А, лямбды! Ну это ж просто пиздец как удобно, если разобраться. По сути, это такой короткий способ написать анонимный метод, который компилятор сам, хитрая жопа, превратит либо в делегат, либо в дерево выражений — Expression<T>. Не надо городить огород с отдельными методами.

Смотри, как это на практике выглядит:

// Вот тебе лямбда, которую сразу суём в делегат Func<int, int, int>
Func<int, int, int> add = (x, y) => x + y;
int result = add(5, 3); // result = 8, ебать колотить!

// А эту же самую хрень можно и в другой делегат запихнуть, если сигнатура сходится
Action<int, int> printSum = (x, y) => Console.WriteLine(x + y);
printSum(5, 3); // Напечатает: 8

А теперь главные фишки, чтобы не облажаться:

  1. Компилятор — не дурак. Он сам сообразит, во что превращать лямбду, глядя на контекст. Главное, чтобы параметры и возвращаемый тип у тебя в лямбде совпадали с тем, что делегат ожидает. Не совпадут — получишь по ебалу ошибкой.
  2. Замыкания — вот где магия, блядь. Лямбда может хавать переменные из того метода, где её объявили. Компилятор для этого невидимый класс создаёт, чтобы эти переменные не потерялись. Смотри:
    int multiplier = 2;
    Func<int, int> multiplierFunc = x => x * multiplier; // Захватил переменную multiplier, и всё работает
  3. Деревья выражений — это уже для продвинутых. Если ты лямбду суёшь не в обычный делегат, а в Expression<TDelegate>, то компилятор не код генерит, а строит дерево выражений — структуру данных, описывающую твою операцию. Это основа всяких LINQ провайдеров, которые в базу запросы переводят.
    // Это уже не исполняемый код, а его описание
    Expression<Func<int, int, int>> expression = (x, y) => x + y;

В общем, если сравнивать с тем, чтобы вручную через new DelegateType() делегаты создавать, то лямбды — это просто небо и земля. Код чище, понятнее, и писать в разы быстрее. Ёпта, красота же!