钱行慕

导航

【译】C#表达式树

原文链接:传送门

表达式树以树状的数据结构来表示代码,树的每一个节点都是一个表达式,比如是一个方法调用,或者一个二元操作符x<y。

你可以编译并运行由表达式树所表示的代码。这可以使你进行可执行代码的动态修改,也可以在不同的数据库中执行LINQ查询,已经创建动态查询。

表达式树也可以用在动态语言运行时以在动态语言以及.NET之间提供可交互性,从而使得编译器作者可以产生表达式树来代替MSIL。

你可以使用C#或者Visual Basic编译器基于匿名lambda表达式为你创建表达式树,或者你也可以使用命名空间System.Linq.Expressions 来手动的创建表达式树。

 从Lambda表达式创建表达式树

当一个Lambda表达式被赋给一个类型为Expression<TDelegate> 的变量时,编译器会产生代码来构建一个表示那个Lambda表达式的表达式树。

C#编译器仅能从表达式Lambdas(或者说是单行的Lambda)来创建表达式树。它不能转换声明式Lambdas(或者多行Lambdas)。

如下代码示例演示了如何使用C#编译器来创建代表了Lambda表达式 num => num < 5 的表达式树。

Expression<Func<int, bool>> lambda = num => num < 5;

使用API来创建表达式树

为了通过使用API来创建表达式树,可以使用Expression类。这个类包含了静态工厂方法,其可以创建特定类型的表达式树节点。比如,ParameterExpression,其代表了一个变量或者一个参数; MethodCallExpression,其代表了一个方法调用。 ParameterExpressionMethodCallExpression 以及其他表达式特定的类型都定义在System.Linq.Expressions 命名空间中,这些类型均继承于抽象类型Expression

如下代码示例演示了如何通过API来创建代表 num => num < 5 的表达式树。

// 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#编译器创建于Lambda表达式的表达式树更加复杂的表达式树。

如下的示例演示了如何创建一个计算一个数的阶乘的表达式树。

// 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.

解析表达式树

如下的示例演示了代表了num => num < 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

表达式树的不可变性

表达式树应该是不可变的。这意味着如果你想要修改一个表达式树,你必须通过拷贝一个已存在的表达式树并替换它的节点来构造一个新的表达式树。你可以使用表达式树访问器来遍历一个已存在的表达式树。

编译表达式树

Expression<TDelegate> 类提供了一个 Compile 方法,其可以将由表达式树代表的代码编译为一个可执行的委托。

如下的代码示例演示了如何编译一个表达式树并执行其结果代码。

// 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.

更多信息,请查看:

posted on 2020-11-06 13:51  钱行慕  阅读(201)  评论(0编辑  收藏  举报