Деревья выражений (C# и Visual Basic)

Деревья выражений представляют код в виде древовидной структуры данных, каждый узел в которой является выражением, например вызовом метода или двоичной операцией, такой как x < y.

Можно компилировать и выполнять код, представленный деревьями выражений. Это обеспечивает возможность динамической модификации выполняемого кода, выполнения запросов LINQ в различных базах данных и создания динамических запросов. Дополнительные сведения о деревьях выражений в LINQ см. в разделах Практическое руководство. Использование деревьев выражений для построения динамических запросов (C# и Visual Basic) и Пошаговое руководство. Создание поставщика IQueryable LINQ.

Кроме того, деревья выражений используются в среде выполнения динамического языка (DLR) для обеспечения взаимодействия между динамическими языками и платформой .NET Framework и предоставления создателям компиляторов возможности выдавать деревья выражений вместо языка MSIL. Дополнительные сведения о среде DLR см. в разделе Общие сведения о среде DLR.

Можно создать дерево выражений на основе анонимного лямбда-выражения с использованием компилятора C# или Visual Basic или создать деревья выражений вручную с использованием пространства имен System.Linq.Expressions.

Создание деревьев выражений из лямбда-выражений

Когда лямбда-выражение назначается переменной с типом Expression<TDelegate>, компилятор выдает код для создания дерева выражений, представляющего лямбда-выражение.

Компиляторы C# и Visual Basic могут создавать деревья выражений только из лямбд выражений (или однострочных лямбд). Эти компиляторы не могут выполнить синтаксический анализ лямбд операторов (или многострочных лямбд). Дополнительные сведения о лямбда-выражениях в C# и Visual Basic см. в разделах Лямбда-выражения (Руководство по программированию в C#) и Лямбда-выражения (Visual Basic) соответственно.

В следующем примере кода демонстрируется способ создания компиляторами C# и Visual Basic дерева выражений, которое представляет лямбда-выражение num => num < 5 (в C#) или Function(num) num < 5 (в Visual Basic).

Dim lambda As Expression(Of Func(Of Integer, Boolean)) =
    Function(num) num < 5
Expression<Func<int, bool>> lambda = num => num < 5;

Создание деревьев выражений с использованием API-интерфейса

Создать деревья выражений с использованием API-интерфейса можно с помощью класса Expression. Этот класс содержит статические методы фабрики, которые создают узлы дерева выражений особых типов, например выражение ParameterExpression, представляющее переменную или параметр, или выражение MethodCallExpression, представляющее вызов метода. Выражения ParameterExpression, MethodCallExpression и другие зависящие от выражения типы также определяются в пространстве имен System.Linq.Expressions. Эти типы являются производными от абстрактного типа Expression.

В следующем примере кода демонстрируется способ создания дерева выражений, которое представляет лямбда-выражение num => num < 5 (в C#) или Function(num) num < 5 (в Visual Basic), с использованием API-интерфейса.


' Import the following namespace to your project: System.Linq.Expressions

' Manually build the expression tree for the lambda expression num => num < 5.
Dim numParam As ParameterExpression = Expression.Parameter(GetType(Integer), "num")
Dim five As ConstantExpression = Expression.Constant(5, GetType(Integer))
Dim numLessThanFive As BinaryExpression = Expression.LessThan(numParam, five)
Dim lambda1 As Expression(Of Func(Of Integer, Boolean)) =
  Expression.Lambda(Of Func(Of Integer, Boolean))(
        numLessThanFive,
        New ParameterExpression() {numParam})

            // Add the following using directive to your code file:
            // using System.Linq.Expressions;

            // Manually build the expression tree for 
            // the lambda expression num => num < 5.
            ParameterExpression numParam = Expression.Parameter(typeof(int), "num");
            ConstantExpression five = Expression.Constant(5, typeof(int));
            BinaryExpression numLessThanFive = Expression.LessThan(numParam, five);
            Expression<Func<int, bool>> lambda1 =
                Expression.Lambda<Func<int, bool>>(
                    numLessThanFive,
                    new ParameterExpression[] { numParam });

В .NET Framework 4 API-интерфейс деревьев выражений также поддерживает назначения и выражения потока управления, такие как циклы, блоки условий и блоки try-catch. С помощью API-интерфейса можно создавать более сложные деревья выражений, чем те, которые создаются компиляторами C# и Visual Basic из лямбда-выражений. В следующем примере показано создание дерева выражений, позволяющего рассчитать факториал числа.

' Creating a parameter expression.
Dim value As ParameterExpression =
    Expression.Parameter(GetType(Integer), "value")

' Creating an expression to hold a local variable. 
Dim result As ParameterExpression =
    Expression.Parameter(GetType(Integer), "result")

' Creating a label to jump to from a loop.
Dim label As LabelTarget = Expression.Label(GetType(Integer))

' Creating a method body.
Dim block As BlockExpression = Expression.Block(
    New ParameterExpression() {result},
    Expression.Assign(result, Expression.Constant(1)),
    Expression.Loop(
        Expression.IfThenElse(
            Expression.GreaterThan(value, Expression.Constant(1)),
            Expression.MultiplyAssign(result,
                Expression.PostDecrementAssign(value)),
            Expression.Break(label, result)
        ),
        label
    )
)

' Compile an expression tree and return a delegate.
Dim factorial As Integer =
    Expression.Lambda(Of Func(Of Integer, Integer))(block, value).Compile()(5)

Console.WriteLine(factorial)
' Prints 120.
// Creating a parameter expression.
ParameterExpression value = Expression.Parameter(typeof(int), "value");

// Creating an expression to hold a local variable. 
ParameterExpression result = Expression.Parameter(typeof(int), "result");

// Creating a label to jump to from a loop.
LabelTarget label = Expression.Label(typeof(int));

// Creating a method body.
BlockExpression block = Expression.Block(
    // Adding a local variable.
    new[] { result },
    // Assigning a constant to a local variable: result = 1
    Expression.Assign(result, Expression.Constant(1)),
    // Adding a loop.
        Expression.Loop(
    // Adding a conditional block into the loop.
           Expression.IfThenElse(
    // Condition: value > 1
               Expression.GreaterThan(value, Expression.Constant(1)),
    // If true: result *= value --
               Expression.MultiplyAssign(result,
                   Expression.PostDecrementAssign(value)),
    // If false, exit the loop and go to the label.
               Expression.Break(label, result)
           ),
    // Label to jump to.
       label
    )
);

// Compile and execute an expression tree.
int factorial = Expression.Lambda<Func<int, int>>(block, value).Compile()(5);

Console.WriteLine(factorial);
// Prints 120.

Дополнительные сведения см. на странице Generating Dynamic Methods with Expression Trees in Visual Studio 2010.

Синтаксический анализ деревьев выражений

В следующем примере кода показано, как дерево выражений, представляющее лямбда-выражение num => num < 5 (C#) или Function(num) num < 5 (Visual Basic), может быть разложено на части.


        ' Import the following namespace to your project: System.Linq.Expressions

        ' Create an expression tree.
        Dim exprTree As Expression(Of Func(Of Integer, Boolean)) = Function(num) num < 5

        ' Decompose the expression tree.
        Dim param As ParameterExpression = exprTree.Parameters(0)
        Dim operation As BinaryExpression = exprTree.Body
        Dim left As ParameterExpression = operation.Left
        Dim right As ConstantExpression = operation.Right

        Console.WriteLine(String.Format("Decomposed expression: {0} => {1} {2} {3}",
                          param.Name, left.Name, operation.NodeType, right.Value))

        ' This code produces the following output:
        '
        ' Decomposed expression: num => num LessThan 5


// Add the following using directive to your code file:
// using System.Linq.Expressions;

// Create an expression tree.
Expression<Func<int, bool>> exprTree = num => num < 5;

// Decompose the expression tree.
ParameterExpression param = (ParameterExpression)exprTree.Parameters[0];
BinaryExpression operation = (BinaryExpression)exprTree.Body;
ParameterExpression left = (ParameterExpression)operation.Left;
ConstantExpression right = (ConstantExpression)operation.Right;

Console.WriteLine("Decomposed expression: {0} => {1} {2} {3}",
                  param.Name, left.Name, operation.NodeType, right.Value);

// This code produces the following output:

// Decomposed expression: num => num LessThan 5            

Неизменность деревьев выражений

Деревья выражений должны быть неизменными. Это означает, что если требуется изменить дерево выражений, следует создать новое дерево выражений копированием существующего дерева, а затем заменить узлы в нем. Можно использовать посетителя дерева выражений для прохода по существующему дереву выражений. Дополнительные сведения см. в разделе Практическое руководство. Изменение деревьев выражений (C# и Visual Basic).

Компиляция деревьев выражений

Тип Expression<TDelegate> предоставляет метод Compile, который компилирует код, представляемый деревом выражений, в исполняемый делегат.

В следующем примере кода показан способ компиляции дерева выражений и запуска полученного кода.

' Creating an expression tree.
Dim expr As Expression(Of Func(Of Integer, Boolean)) =
    Function(num) num < 5

' Compiling the expression tree into a delegate.
Dim result As Func(Of Integer, Boolean) = expr.Compile()

' Invoking the delegate and writing the result to the console.
Console.WriteLine(result(4))

' Prints True.

' You can also use simplified syntax
' to compile and run an expression tree.
' The following line can replace two previous statements.
Console.WriteLine(expr.Compile()(4))

' Also prints True.
// Creating an expression tree.
Expression<Func<int, bool>> expr = num => num < 5;

// Compiling the expression tree into a delegate.
Func<int, bool> result = expr.Compile();

// Invoking the delegate and writing the result to the console.
Console.WriteLine(result(4));

// Prints True.

// You can also use simplified syntax
// to compile and run an expression tree.
// The following line can replace two previous statements.
Console.WriteLine(expr.Compile()(4));

// Also prints True.

Дополнительные сведения см. в разделе Практическое руководство. Выполнение деревьев выражений (C# и Visual Basic).

См. также

Задачи

Практическое руководство. Выполнение деревьев выражений (C# и Visual Basic)

Практическое руководство. Изменение деревьев выражений (C# и Visual Basic)

Ссылки

Лямбда-выражения (Руководство по программированию в C#)

System.Linq.Expressions

Основные понятия

Общие сведения о среде DLR

Лямбда-выражения (Visual Basic)

Другие ресурсы

Expression Tree Basics

Generating Dynamic Methods with Expression Trees in Visual Studio 2010

Основные понятия программирования