Поддержка среды выполнения .NET для деревьев выражений

Существует большой список классов в среде выполнения .NET, которые работают с деревьями выражений. Полный список можно просмотреть в разделе System.Linq.Expressions. Вместо перечисления полного списка давайте посмотрим, как были разработаны классы среды выполнения.

Согласно принципам языка выражение — это блок кода, который выполняет вычисления и возвращает значение. Выражения могут быть простыми: константное выражение 1 возвращает константное значение 1. Они могут быть более сложными: выражение (-B + Math.Sqrt(B*B - 4 * A * C)) / (2 * A) возвращает один корень квадратного уравнения (если у уравнения есть решение).

System.Linq.Expression и производные типы

Одна из трудностей при работе с деревьями выражений заключается в том, что выражения различных типов могут использоваться в программах в самых разных местах. Возьмем для примера выражение присваивания. В правой его части может быть константа, переменная, выражение вызова метода или иной элемент. Такая гибкость языка означает, что при обходе дерева выражения в его узлах вам могут встретиться различные типы выражений. Поэтому при работе с типом базового выражения это самый простой способ работы. Однако иногда этого недостаточно. На этот случай базовый класс Expression содержит свойство NodeType. Возвращает значение ExpressionType, которое представляет собой перечисление возможных типов выражений. После того как вы знаете тип узла, вы приведете его к такому типу и выполните определенные действия, зная тип узла выражения. Можно выполнить поиск узлов определенных типов, а затем работать со свойствами данного типа выражения.

Например, этот код выводит имя переменной для выражения доступа к переменной. В следующем коде показано, как проверка тип узла, а затем приведение к выражению доступа к переменной, а затем проверка свойства определенного типа выражения:

Expression<Func<int, int>> addFive = (num) => num + 5;

if (addFive is LambdaExpression lambdaExp)
{
    var parameter = lambdaExp.Parameters[0];  -- first

    Console.WriteLine(parameter.Name);
    Console.WriteLine(parameter.Type);
}

Создание деревьев выражений

Класс System.Linq.Expression также содержит множество статических методов для создания выражений. Эти методы создают узел выражения с помощью аргументов, предоставленных для его дочерних элементов. Таким образом, вы создаете выражение с конечных узлов. Например, следующий код создает выражение сложения:

// Addition is an add expression for "1 + 2"
var one = Expression.Constant(1, typeof(int));
var two = Expression.Constant(2, typeof(int));
var addition = Expression.Add(one, two);

На этом простом примере видно, что в создании деревьев выражений и работе с ними задействовано множество типов. Такая сложность необходима для предоставления широких возможностей словаря C#.

Существуют типы узлов выражений, которые сопоставляются почти со всеми элементами синтаксиса языка C#. Каждый тип имеет определенные методы для конкретного типа элементов языка. Держать в голове приходится слишком многое. Вместо того чтобы попытаться запомнить все, вот методы, которые вы используете для работы с деревьями выражений:

  1. Просмотрите элементы перечисления ExpressionType, чтобы определить возможные узлы для анализа. Этот список помогает, когда вы хотите пройти и понять дерево выражений.
  2. Просмотрите статические члены класса Expression, которые служат для создания выражения. С помощью этих методов можно создать выражение любого типа на основе набора дочерних узлов.
  3. Обратите внимание на класс ExpressionVisitor, который служит для создания измененного дерева выражения.

Вы найдете больше, как вы посмотрите на каждую из этих трех областей. Неизменно, вы найдете то, что вам нужно, когда вы начинаете с одного из этих трех шагов.