C# 知识回顾 - 表达式树 Expression Trees


目录


简介

表达式树以树形数据结构表示代码,其中每一个节点都是一种表达式,比如方法调用和 x < y 这样的二元运算等。

你可以对表达式树中的代码进行编辑和运算。这样能够动态修改可执行代码、在不同数据库中执行 LINQ 查询以及创建动态查询。

表达式树还能用于动态语言运行时 (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

C# 表达式树 Expression Trees知识总结_代码示例

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

C# 表达式树 Expression Trees知识总结_表达式树_02

代码使用 ​​Expression​​ 类的静态方法进行创建。


三、解析表达式树

下列代码示例展示如何分解表示 lambda 表达式 num => num == 0 的表达式树。

C# 表达式树 Expression Trees知识总结_代码示例_03

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}");

C# 表达式树 Expression Trees知识总结_代码示例_04

C# 表达式树 Expression Trees知识总结_表达式树_05




四、表达式树永久性

表达式树应具有永久性(类似字符串)。这意味着如果你想修改某个表达式树,则必须复制该表达式树然后替换其中的节点来创建一个新的表达式树。 你可以使用表达式树访问者遍历现有表达式树。第七节介绍了如何修改表达式树。

五、编译表达式树

​Expression<TDelegate>​​​ 类型提供了 ​​Compile​​ 方法以将表达式树表示的代码编译成可执行委托。

C# 表达式树 Expression Trees知识总结_代码示例_06

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!"));

C# 表达式树 Expression Trees知识总结_可执行_07

C# 表达式树 Expression Trees知识总结_自动生成_08





六、执行表达式树

执行表达式树可能会返回一个值,也可能仅执行一个操作(例如调用方法)。

只能执行表示 lambda 表达式的表达式树。表示 lambda 表达式的表达式树属于 ​​LambdaExpression​​​ 或 ​​Expression<TDelegate>​​​ 类型。若要执行这些表达式树,需要调用 ​​Compile​​​ 方法来创建一个可执行委托,然后调用该委托。 C# 表达式树 Expression Trees知识总结_代码示例_09

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()}");

C# 表达式树 Expression Trees知识总结_c#_10

C# 表达式树 Expression Trees知识总结_自动生成_11


七、修改表达式树

该类继承 ​​ExpressionVisitor​​ 类,通过 Visit 方法间接调用 VisitBinary 方法将 != 替换成 ==。基类方法构造类似于传入的表达式树的节点,但这些节点将其子目录树替换为访问器递归生成的表达式树。

C# 表达式树 Expression Trees知识总结_表达式树_12

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     }

C# 表达式树 Expression Trees知识总结_c#_13

C# 表达式树 Expression Trees知识总结_可执行_14




八、调试

8.1 参数表达式


1             ParameterExpression pExpression1 = Expression.Parameter(typeof(string)); 2             ParameterExpression pExpression2 = Expression.Parameter(typeof(string), "msg");


C# 表达式树 Expression Trees知识总结_代码示例_15

图8-1

C# 表达式树 Expression Trees知识总结_代码示例_16

图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);


C# 表达式树 Expression Trees知识总结_表达式树_17

图8-3

C# 表达式树 Expression Trees知识总结_可执行_18

图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);


C# 表达式树 Expression Trees知识总结_表达式树_19

图8-5

C# 表达式树 Expression Trees知识总结_c#_20

图8-6

观察 DebugView ,如果 lambda 表达式没有名称,则会为其分配一个自动生成的名称。





C# 知识回顾 - 表达式树 Expression Trees


目录


简介

表达式树以树形数据结构表示代码,其中每一个节点都是一种表达式,比如方法调用和 x < y 这样的二元运算等。

你可以对表达式树中的代码进行编辑和运算。这样能够动态修改可执行代码、在不同数据库中执行 LINQ 查询以及创建动态查询。


表达式树还能用于动态语言运行时 (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

C# 表达式树 Expression Trees知识总结_自动生成_21

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

C# 表达式树 Expression Trees知识总结_代码示例_22

代码使用 ​​Expression​​ 类的静态方法进行创建。


三、解析表达式树

下列代码示例展示如何分解表示 lambda 表达式 num => num == 0 的表达式树。

C# 表达式树 Expression Trees知识总结_表达式树_23

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}");

C# 表达式树 Expression Trees知识总结_可执行_24

C# 表达式树 Expression Trees知识总结_表达式树_05




四、表达式树永久性

表达式树应具有永久性(类似字符串)。这意味着如果你想修改某个表达式树,则必须复制该表达式树然后替换其中的节点来创建一个新的表达式树。 你可以使用表达式树访问者遍历现有表达式树。第七节介绍了如何修改表达式树。

五、编译表达式树

​Expression<TDelegate>​​​ 类型提供了 ​​Compile​​ 方法以将表达式树表示的代码编译成可执行委托。

C# 表达式树 Expression Trees知识总结_c#_26

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!"));

C# 表达式树 Expression Trees知识总结_可执行_27

C# 表达式树 Expression Trees知识总结_自动生成_08





六、执行表达式树

执行表达式树可能会返回一个值,也可能仅执行一个操作(例如调用方法)。

只能执行表示 lambda 表达式的表达式树。表示 lambda 表达式的表达式树属于 ​​LambdaExpression​​​ 或 ​​Expression<TDelegate>​​​ 类型。若要执行这些表达式树,需要调用 ​​Compile​​​ 方法来创建一个可执行委托,然后调用该委托。 C# 表达式树 Expression Trees知识总结_代码示例_29

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()}");

C# 表达式树 Expression Trees知识总结_表达式树_30

C# 表达式树 Expression Trees知识总结_自动生成_11


七、修改表达式树

该类继承 ​​ExpressionVisitor​​ 类,通过 Visit 方法间接调用 VisitBinary 方法将 != 替换成 ==。基类方法构造类似于传入的表达式树的节点,但这些节点将其子目录树替换为访问器递归生成的表达式树。

C# 表达式树 Expression Trees知识总结_c#_32

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     }

C# 表达式树 Expression Trees知识总结_自动生成_33

C# 表达式树 Expression Trees知识总结_可执行_14




八、调试

8.1 参数表达式


1             ParameterExpression pExpression1 = Expression.Parameter(typeof(string)); 2             ParameterExpression pExpression2 = Expression.Parameter(typeof(string), "msg");


C# 表达式树 Expression Trees知识总结_代码示例_15

图8-1

C# 表达式树 Expression Trees知识总结_代码示例_16

图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);


C# 表达式树 Expression Trees知识总结_表达式树_17

图8-3

C# 表达式树 Expression Trees知识总结_可执行_18

图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);


C# 表达式树 Expression Trees知识总结_表达式树_19

图8-5

C# 表达式树 Expression Trees知识总结_c#_20

图8-6

观察 DebugView ,如果 lambda 表达式没有名称,则会为其分配一个自动生成的名称。