Ответ
Дерево выражений (Expression Tree) — это древовидная структура данных, которая представляет код на этапе выполнения в виде данных, а не исполняемых инструкций. Это позволяет анализировать, модифицировать или транслировать код (например, в SQL-запрос) перед его выполнением.
Основное отличие от делегата:
- Делегат
Func<int, int>:x => x + 1— это скомпилированный код для немедленного выполнения. - Выражение
Expression<Func<int, int>>:x => x + 1— это данные, описывающие структуру этого лямбда-выражения (узел сложения с параметромxи константой1).
Пример создания и компиляции дерева выражений "вручную":
using System.Linq.Expressions;
// Цель: создать выражение, эквивалентное (x) => x * x + 2
// 1. Параметр выражения.
ParameterExpression paramX = Expression.Parameter(typeof(int), "x");
// 2. Константа '2'.
ConstantExpression constantTwo = Expression.Constant(2, typeof(int));
// 3. Умножение: x * x.
BinaryExpression multiply = Expression.Multiply(paramX, paramX);
// 4. Сложение: (x * x) + 2.
BinaryExpression addBody = Expression.Add(multiply, constantTwo);
// 5. Построение лямбда-выражения.
Expression<Func<int, int>> lambdaExpression =
Expression.Lambda<Func<int, int>>(addBody, paramX);
// 6. Дерево выражений можно проанализировать.
Console.WriteLine(lambdaExpression.Body); // Выведет: ((x * x) + 2)
// 7. Компиляция дерева в исполняемый делегат.
Func<int, int> compiledDelegate = lambdaExpression.Compile();
// 8. Вызов.
int result = compiledDelegate(5); // (5 * 5) + 2 = 27
Console.WriteLine(result);
Практическое применение:
- LINQ to SQL/Entity Framework: Когда вы пишете
db.Users.Where(u => u.Age > 18), выражениеu => u.Age > 18передаётся какExpression<Func<User, bool>>. Поставщик EF анализирует это дерево и преобразует его в SQLWHERE Age > 18, а не выполняет фильтрацию в памяти. - Динамическая генерация кода: Создание специализированных методов на лету для оптимизации.
- Валидация и анализ кода: Проверка структуры выражений перед выполнением.
Ответ 18+ 🔞
Давай разберёмся, что за зверь такой — дерево выражений. Представь, что ты пишешь лямбду, типа x => x + 1. Обычно это просто функция, которую можно вызвать — и всё, поезд ушёл. Но если обернуть её в Expression<Func<int, int>>, то вместо готового кода ты получаешь, по сути, схему этого кода. Как чертёж двигателя вместо самого двигателя. И с этим чертежом можно делать что угодно: разобрать по винтикам, пересобрать или, например, перевести на язык SQL. Это и есть Expression Tree.
Чем отличается от обычного делегата?
Func<int, int>— это уже скомпилированная инструкция «взять число и прибавить единицу». Выполняй — и не морочь голову.Expression<Func<int, int>>— это данные, которые говорят: «слушай, тут у нас операция сложения, слева — параметр с именем 'x', справа — константа '1'». И всё это в виде дерева объектов, которое можно обойти.
Собираем дерево своими руками, как конструктор
Вот тебе пример, как собрать выражение x => x * x + 2 с нуля, из кусочков:
using System.Linq.Expressions;
// Хотим собрать (x) => x * x + 2
// 1. Сначала объявляем параметр — нашу 'x'.
ParameterExpression paramX = Expression.Parameter(typeof(int), "x");
// 2. Константа '2'. Просто число.
ConstantExpression constantTwo = Expression.Constant(2, typeof(int));
// 3. Умножаем x на x. Получаем узел умножения.
BinaryExpression multiply = Expression.Multiply(paramX, paramX);
// 4. К результату умножения прибавляем двойку. Получаем тело выражения.
BinaryExpression addBody = Expression.Add(multiply, constantTwo);
// 5. Упаковываем всё это в лямбда-выражение, указывая параметр.
Expression<Func<int, int>> lambdaExpression =
Expression.Lambda<Func<int, int>>(addBody, paramX);
// 6. Можем посмотреть, что внутри. Это же данные!
Console.WriteLine(lambdaExpression.Body); // Напечатает: ((x * x) + 2)
// 7. Но если надо — компилируем это дерево обратно в быстрый делегат.
Func<int, int> compiledDelegate = lambdaExpression.Compile();
// 8. И выполняем.
int result = compiledDelegate(5); // (5 * 5) + 2 = 27
Console.WriteLine(result);
Зачем это всё, спрашивается, нужно?
А вот, блядь, где начинается магия:
-
Вся мощь Entity Framework / LINQ to SQL построена на этом. Когда ты пишешь
db.Users.Where(u => u.Age > 18), твоё условиеu => u.Age > 18приходит не как готовая функция, а именно как дерево выражений. EF, этот хитрожопый фреймворк, разбирает это дерево, видит там обращение к свойствуAgeи операцию «больше», и переводит это в чистый SQL:WHERE Age > 18. Если бы это была обычная функция, он бы тупо выгрузил всю таблицу и фильтровал в памяти — пиздец производительности. -
Динамическая генерация кода. Можно на лету собирать оптимальные методы под конкретную задачу, когда заранее не знаешь, что понадобится.
-
Анализ и валидация. Можешь поковыряться в «чертеже» кода, прежде чем его запускать. Проверить, нет ли там подозрительных операций.
Короче, дерево выражений — это как промежуточное представление кода, которое даёт тебе власть над тем, что, как и когда будет выполняться. Не просто «выполни», а «дай-ка я посмотрю, что ты там такое написал, и может, переделаю под себя». Очень мощная штука, когда нужно оторваться от прямого исполнения и заняться метапрограммированием.