Поделиться через


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

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

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

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

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

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

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

Компиляторы C# и Visual Basic могут создавать деревья выражений только на основе лямбд выражений (или однострочных лямбд). Они не могут анализировать лямбды операторов (или многострочные лямбды). Дополнительные сведения о лямбда-выражениях на языке C# см. в разделе Лямбда-выражения (Руководство по программированию в C#); для Visual Basic см. Лямбда-выражения (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.

Дополнительные сведения см. в разделе Создание динамических методов с использованием деревьев выражений в 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 предоставляет метод 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)

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

Основы деревьев выражений

Создание динамических методов с использованием деревьев выражений в Visual Studio 2010

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