来源: [C#] C# 知识回顾 – 表达式树 Expression Trees – 反骨仔 – 博客园
目录
简介
表达式树以树形数据结构表示代码,其中每一个节点都是一种表达式,比如方法调用和 x < y 这样的二元运算等。
表达式树还能用于动态语言运行时 (DLR) 以提供动态语言和 .NET Framework 之间的互操作性。
一、Lambda 表达式创建表达式树
若 lambda 表达式被分配给 Expression<TDelegate> 类型的变量,则编译器可以发射代码以创建表示该 lambda 表达式的表达式树。
C# 编译器只能从表达式 lambda (或单行 lambda)生成表达式树。
下列代码示例使用关键字 Expression创建表示 lambda 表达式:
1 Expression<Action<int>> actionExpression = n => Console.WriteLine(n); 2 Expression<Func<int, bool>> funcExpression1 = (n) => n < 0; 3 Expression<Func<int, int, bool>> funcExpression2 = (n, m) => n - m == 0;
二、API 创建表达式树
通过 API 创建表达式树需要使用 Expression 类
下列代码示例展示如何通过 API 创建表示 lambda 表达式:num => num == 0
1 //通过 Expression 类创建表达式树 2 // lambda:num => num == 0 3 ParameterExpression pExpression = Expression.Parameter(typeof(int)); //参数:num 4 ConstantExpression cExpression = Expression.Constant(0); //常量:0 5 BinaryExpression bExpression = Expression.MakeBinary(ExpressionType.Equal, pExpression, cExpression); //表达式:num == 0 6 Expression<Func<int, bool>> lambda = Expression.Lambda<Func<int, bool>>(bExpression, pExpression); //lambda 表达式:num => num == 0
代码使用 Expression 类的静态方法进行创建。
三、解析表达式树
下列代码示例展示如何分解表示 lambda 表达式 num => num == 0 的表达式树。
1 Expression<Func<int, bool>> funcExpression = num => num == 0; 2 3 //开始解析 4 ParameterExpression pExpression = funcExpression.Parameters[0]; //lambda 表达式参数 5 BinaryExpression body = (BinaryExpression)funcExpression.Body; //lambda 表达式主体:num == 0 6 7 Console.WriteLine($"解析:{pExpression.Name} => {body.Left} {body.NodeType} {body.Right}");
四、表达式树永久性
五、编译表达式树
Expression<TDelegate> 类型提供了 Compile 方法以将表达式树表示的代码编译成可执行委托。
1 //创建表达式树 2 Expression<Func<string, int>> funcExpression = msg => msg.Length; 3 //表达式树编译成委托 4 var lambda = funcExpression.Compile(); 5 //调用委托 6 Console.WriteLine(lambda("Hello, World!")); 7 8 //语法简化 9 Console.WriteLine(funcExpression.Compile()("Hello, World!"));
六、执行表达式树
执行表达式树可能会返回一个值,也可能仅执行一个操作(例如调用方法)。
1 const int n = 1; 2 const int m = 2; 3 4 //待执行的表达式树 5 BinaryExpression bExpression = Expression.Add(Expression.Constant(n), Expression.Constant(m)); 6 //创建 lambda 表达式 7 Expression<Func<int>> funcExpression = Expression.Lambda<Func<int>>(bExpression); 8 //编译 lambda 表达式 9 Func<int> func = funcExpression.Compile(); 10 11 //执行 lambda 表达式 12 Console.WriteLine($"{n} + {m} = {func()}");
七、修改表达式树
该类继承 ExpressionVisitor 类,通过 Visit 方法间接调用 VisitBinary 方法将 != 替换成 ==。基类方法构造类似于传入的表达式树的节点,但这些节点将其子目录树替换为访问器递归生成的表达式树。
1 internal class Program 2 { 3 private static void Main(string[] args) 4 { 5 Expression<Func<int, bool>> funcExpression = num => num == 0; 6 Console.WriteLine($"Source: {funcExpression}"); 7 8 var visitor = new NotEqualExpressionVisitor(); 9 var expression = visitor.Visit(funcExpression); 10 11 Console.WriteLine($"Modify: {expression}"); 12 13 Console.Read(); 14 } 15 16 /// <summary> 17 /// 不等表达式树访问器 18 /// </summary> 19 public class NotEqualExpressionVisitor : ExpressionVisitor 20 { 21 public Expression Visit(BinaryExpression node) 22 { 23 return VisitBinary(node); 24 } 25 26 protected override Expression VisitBinary(BinaryExpression node) 27 { 28 return node.NodeType == ExpressionType.Equal 29 ? Expression.MakeBinary(ExpressionType.NotEqual, node.Left, node.Right) //重新弄个表达式:用 != 代替 == 30 : base.VisitBinary(node); 31 } 32 } 33 }
八、调试
8.1 参数表达式
1 ParameterExpression pExpression1 = Expression.Parameter(typeof(string)); 2 ParameterExpression pExpression2 = Expression.Parameter(typeof(string), "msg");
图8-1
图8-2
从 DebugView 可知,如果参数没有名称,则会为其分配一个自动生成的名称。
1 const int num1 = 250; 2 const float num2 = 250; 3 4 ConstantExpression cExpression1 = Expression.Constant(num1); 5 ConstantExpression cExpression2 = Expression.Constant(num2);
图8-3
图8-4
从 DebugView 可知,float 比 int 多了个后缀 F。
1 Expression lambda1 = Expression.Lambda<Func<int>>(Expression.Constant(250)); 2 Expression lambda2 = Expression.Lambda<Func<int>>(Expression.Constant(250), "CustomName", null);
图8-5
图8-6
观察 DebugView ,如果 lambda 表达式没有名称,则会为其分配一个自动生成的名称。