表示式樹 Expression Trees
目錄
簡介
表示式樹以樹形資料結構表示程式碼,其中每一個節點都是一種表示式,比如方法呼叫和x < y這樣的二元運算等。
你可以對錶達式樹中的程式碼進行編輯和運算。這樣能夠動態修改可執行程式碼、在不同資料庫中執行 LINQ 查詢以及建立動態查詢。表示式樹還能用於動態語言執行時 (DLR) 以提供動態語言和 .NET Framework 之間的互操作性。
一、Lambda 表示式建立表示式樹
若 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!"));
六、執行表示式樹
執行表示式樹可能會返回一個值,也可能僅執行一個操作(例如呼叫方法)。
只能執行表示 lambda 表示式的表示式樹。表示 lambda 表示式的表示式樹屬於LambdaExpression或Expression<TDelegate>型別。若要執行這些表示式樹,需要呼叫Compile方法來建立一個可執行委託,然後呼叫該委託。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 表示式沒有名稱,則會為其分配一個自動生成的名稱。
*********轉摘:https://www.cnblogs.com/liqingwen/p/5868688.html