C#3.0新增功能10 表達式樹 05 解釋表達式

表達式樹中的每一個節點將是派生自 Expression 的類的對象。html

該設計使得訪問表達式樹中的全部節點成爲相對直接的遞歸操做。 常規策略是從根節點開始並肯定它是哪一種節點。node

若是節點類型具備子級,則以遞歸方式訪問該子級。 在每一個子節點中,重複在根節點處使用的步驟:肯定類型,且若是該類型具備子級,則訪問每一個子級。算法

檢查不具備子級的表達式
讓咱們首先訪問一個很是簡單的表達式樹中的每一個節點。 下面是建立常數表達式而後檢查其屬性的代碼:
var constant = Expression.Constant(24, typeof(int));

Console.WriteLine($"This is a/an {constant.NodeType} expression type");
Console.WriteLine($"The type of the constant value is {constant.Type}");
Console.WriteLine($"The value of the constant value is {constant.Value}");
將打印如下內容:
This is an Constant expression type
The type of the constant value is System.Int32
The value of the constant value is 24
檢查一個簡單的加法表達式
從本節簡介處的加法示例開始。
Expression<Func<int>> sum = () => 1 + 2;

沒有使用 var 來聲明此表達式樹,由於此操做沒法執行,這是因爲賦值右側是隱式類型而致使的。
不能使用隱式類型化變量聲明來聲明 lambda 表達式。 它會對編譯器形成循環邏輯問題。 var 聲明會告知編譯器經過賦值運算符右側的表達式的類型查明變量的類型。 Lambda 表達式沒有編譯時類型,可是可轉換爲任何匹配委託或表達式類型。 將 lambda 表達式分配給委託或表達式類型的變量時,可告知編譯器嘗試並將 lambda 表達式轉換爲與「分配對象」變量的簽名匹配的表達式或委託。 編譯器必須嘗試使賦值右側的內容與賦值左側的類型匹配。 賦值兩側都沒法告知編譯器查看賦值運算符另外一側的對象並查看個人類型是否匹配。
express

根節點是 LambdaExpression。 爲了得到 => 運算符右側的有用代碼,須要找到 LambdaExpression 的子級之一。 咱們將經過本部分中的全部表達式來實現此目的。 父節點確實有助於找到 LambdaExpression 的返回類型。api

若要檢查此表達式中的每一個節點,將須要以遞歸方式訪問大量節點。 下面是一個簡單的首次實現:ide

 1 Expression<Func<int, int, int>> addition = (a, b) => a + b;
 2 
 3 Console.WriteLine($"This expression is a {addition.NodeType} expression type");
 4 Console.WriteLine($"The name of the lambda is {((addition.Name == null) ? "<null>" : addition.Name)}");
 5 Console.WriteLine($"The return type is {addition.ReturnType.ToString()}");
 6 Console.WriteLine($"The expression has {addition.Parameters.Count} arguments. They are:");
 7 foreach(var argumentExpression in addition.Parameters)
 8 {
 9     Console.WriteLine($"\tParameter Type: {argumentExpression.Type.ToString()}, Name: {argumentExpression.Name}");
10 }
11 
12 var additionBody = (BinaryExpression)addition.Body;
13 Console.WriteLine($"The body is a {additionBody.NodeType} expression");
14 Console.WriteLine($"The left side is a {additionBody.Left.NodeType} expression");
15 var left = (ParameterExpression)additionBody.Left;
16 Console.WriteLine($"\tParameter Type: {left.Type.ToString()}, Name: {left.Name}");
17 Console.WriteLine($"The right side is a {additionBody.Right.NodeType} expression");
18 var right= (ParameterExpression)additionBody.Right;
19 Console.WriteLine($"\tParameter Type: {right.Type.ToString()}, Name: {right.Name}");

此示例打印如下輸出:函數

This expression is a Lambda expression type
The name of the lambda is <null>
The return type is System.Int32
The expression has 2 arguments. They are:
        Parameter Type: System.Int32, Name: a
        Parameter Type: System.Int32, Name: b
The body is a Add expression
The left side is a Parameter expression
        Parameter Type: System.Int32, Name: a
The right side is a Parameter expression
        Parameter Type: System.Int32, Name: b

以上代碼示例中中包含大量重複。 爲了將其其清理乾淨,並生成一個更加通用的表達式節點訪問者。 這將要求編寫遞歸算法。 任何節點均可能是具備子級的類型。 具備子級的任何節點都要求訪問這些子級並肯定該節點是什麼。 下面是利用遞歸訪問加法運算的已優化的版本:優化

  1 // Visitor 基類:
  2 public abstract class Visitor
  3 {
  4     private readonly Expression node;
  5 
  6     protected Visitor(Expression node)
  7     {
  8         this.node = node;
  9     }
 10 
 11     public abstract void Visit(string prefix);
 12 
 13     public ExpressionType NodeType => this.node.NodeType;
 14     public static Visitor CreateFromExpression(Expression node)
 15     {
 16         switch(node.NodeType)
 17         {
 18             case ExpressionType.Constant:
 19                 return new ConstantVisitor((ConstantExpression)node);
 20             case ExpressionType.Lambda:
 21                 return new LambdaVisitor((LambdaExpression)node);
 22             case ExpressionType.Parameter:
 23                 return new ParameterVisitor((ParameterExpression)node);
 24             case ExpressionType.Add:
 25                 return new BinaryVisitor((BinaryExpression)node);
 26             default:
 27                 Console.Error.WriteLine($"Node not processed yet: {node.NodeType}");
 28                 return default(Visitor);
 29         }
 30     }
 31 }
 32 
 33 // Lambda Visitor
 34 public class LambdaVisitor : Visitor
 35 {
 36     private readonly LambdaExpression node;
 37     public LambdaVisitor(LambdaExpression node) : base(node)
 38     {
 39         this.node = node;
 40     }
 41 
 42     public override void Visit(string prefix)
 43     {
 44         Console.WriteLine($"{prefix}This expression is a {NodeType} expression type");
 45         Console.WriteLine($"{prefix}The name of the lambda is {((node.Name == null) ? "<null>" : node.Name)}");
 46         Console.WriteLine($"{prefix}The return type is {node.ReturnType.ToString()}");
 47         Console.WriteLine($"{prefix}The expression has {node.Parameters.Count} argument(s). They are:");
 48         // Visit each parameter:
 49         foreach (var argumentExpression in node.Parameters)
 50         {
 51             var argumentVisitor = Visitor.CreateFromExpression(argumentExpression);
 52             argumentVisitor.Visit(prefix + "\t");
 53         }
 54         Console.WriteLine($"{prefix}The expression body is:");
 55         // Visit the body:
 56         var bodyVisitor = Visitor.CreateFromExpression(node.Body);
 57         bodyVisitor.Visit(prefix + "\t");
 58     }
 59 }
 60 
 61 // 二元運算 Visitor:
 62 public class BinaryVisitor : Visitor
 63 {
 64     private readonly BinaryExpression node;
 65     public BinaryVisitor(BinaryExpression node) : base(node)
 66     {
 67         this.node = node;
 68     }
 69 
 70     public override void Visit(string prefix)
 71     {
 72         Console.WriteLine($"{prefix}This binary expression is a {NodeType} expression");
 73         var left = Visitor.CreateFromExpression(node.Left);
 74         Console.WriteLine($"{prefix}The Left argument is:");
 75         left.Visit(prefix + "\t");
 76         var right = Visitor.CreateFromExpression(node.Right);
 77         Console.WriteLine($"{prefix}The Right argument is:");
 78         right.Visit(prefix + "\t");
 79     }
 80 }
 81 
 82 // 參數 visitor:
 83 public class ParameterVisitor : Visitor
 84 {
 85     private readonly ParameterExpression node;
 86     public ParameterVisitor(ParameterExpression node) : base(node)
 87     {
 88         this.node = node;
 89     }
 90 
 91     public override void Visit(string prefix)
 92     {
 93         Console.WriteLine($"{prefix}This is an {NodeType} expression type");
 94         Console.WriteLine($"{prefix}Type: {node.Type.ToString()}, Name: {node.Name}, ByRef: {node.IsByRef}");
 95     }
 96 }
 97 
 98 // 常量 visitor:
 99 public class ConstantVisitor : Visitor
100 {
101     private readonly ConstantExpression node;
102     public ConstantVisitor(ConstantExpression node) : base(node)
103     {
104         this.node = node;
105     }
106 
107     public override void Visit(string prefix)
108     {
109         Console.WriteLine($"{prefix}This is an {NodeType} expression type");
110         Console.WriteLine($"{prefix}The type of the constant value is {node.Type}");
111         Console.WriteLine($"{prefix}The value of the constant value is {node.Value}");
112     }
113 }

此算法是能夠訪問任意 LambdaExpression 的算法的基礎。 其中有大量缺口,即代表我建立的代碼僅查找它可能遇到的表達式樹節點組的一小部分。 可是,你仍能夠從其結果中獲益匪淺。 (遇到新的節點類型時,Visitor.CreateFromExpression 方法中的默認 case 會將消息打印到錯誤控制檯。 如此,你便知道要添加新的表達式類型。)ui

在上面所示的加法表達式中運行此訪問者時,將得到如下輸出:this

This expression is a/an Lambda expression type
The name of the lambda is <null>
The return type is System.Int32
The expression has 2 argument(s). They are:
        This is an Parameter expression type
        Type: System.Int32, Name: a, ByRef: False
        This is an Parameter expression type
        Type: System.Int32, Name: b, ByRef: False
The expression body is:
        This binary expression is a Add expression
        The Left argument is:
                This is an Parameter expression type
                Type: System.Int32, Name: a, ByRef: False
        The Right argument is:
                This is an Parameter expression type
                Type: System.Int32, Name: b, ByRef: False
檢查具備許多級別的加法表達式
嘗試更復雜的示例,但仍限制節點類型僅爲加法:
Expression<Func<int>> sum = () => 1 + 2 + 3 + 4;

在訪問者算法上運行此表達式以前,請嘗試思考可能的輸出是什麼。 請記住,+ 運算符是二元運算符:它必須具備兩個子級,分別表示左右操做數。 有幾種可行的方法來構造可能正確的樹:

Expression<Func<int>> sum1 = () => 1 + (2 + (3 + 4));
Expression<Func<int>> sum2 = () => ((1 + 2) + 3) + 4;

Expression<Func<int>> sum3 = () => (1 + 2) + (3 + 4);
Expression<Func<int>> sum4 = () => 1 + ((2 + 3) + 4);
Expression<Func<int>> sum5 = () => (1 + (2 + 3)) + 4;

能夠看到可能的答案分爲兩種,以便着重於最有可能正確的答案。 第一種表示右結合表達式。 第二種表示左結合表達式。 這兩種格式的優勢是,格式能夠縮放爲任意數量的加法表達式。

若是確實經過該訪問者運行此表達式,則將看到此輸出,其驗證簡單的加法表達式是否爲左結合

爲了運行此示例並查看完整的表達式樹,我不得不對源表達式樹進行一次更改。 當表達式樹包含全部常量時,所獲得的樹僅包含 10 的常量值。 編譯器執行全部加法運算,並將表達式縮減爲其最簡單的形式。 只需在表達式中添加一個變量便可看到原始的樹:

Expression<Func<int, int>> sum = (a) => 1 + a + 3 + 4;

建立可得出此總和的訪問者並運行該訪問者,則會看到如下輸出:

 1 This expression is a/an Lambda expression type
 2 The name of the lambda is <null>
 3 The return type is System.Int32  4 The expression has 1 argument(s). They are:
 5         This is an Parameter expression type
 6         Type: System.Int32, Name: a, ByRef: False  7 The expression body is:
 8         This binary expression is a Add expression
 9         The Left argument is:
10                 This binary expression is a Add expression
11                 The Left argument is:
12                         This binary expression is a Add expression
13                         The Left argument is:
14                                 This is an Constant expression type
15                                 The type of the constant value is System.Int32 16                                 The value of the constant value is 1
17                         The Right argument is:
18                                 This is an Parameter expression type
19                                 Type: System.Int32, Name: a, ByRef: False 20                 The Right argument is:
21                         This is an Constant expression type
22                         The type of the constant value is System.Int32 23                         The value of the constant value is 3
24         The Right argument is:
25                 This is an Constant expression type
26                 The type of the constant value is System.Int32 27                 The value of the constant value is 4

還能夠經過訪問者代碼運行任何其餘示例,並查看其表示的樹。 下面是上述 sum3 表達式(使用附加參數來阻止編譯器計算常量)的一個示例:

Expression<Func<int, int, int>> sum3 = (a, b) => (1 + a) + (3 + b);

下面是訪問者的輸出:

 1 This expression is a/an Lambda expression type
 2 The name of the lambda is <null>
 3 The return type is System.Int32  4 The expression has 2 argument(s). They are:
 5         This is an Parameter expression type
 6         Type: System.Int32, Name: a, ByRef: False  7         This is an Parameter expression type
 8         Type: System.Int32, Name: b, ByRef: False  9 The expression body is:
10         This binary expression is a Add expression
11         The Left argument is:
12                 This binary expression is a Add expression
13                 The Left argument is:
14                         This is an Constant expression type
15                         The type of the constant value is System.Int32 16                         The value of the constant value is 1
17                 The Right argument is:
18                         This is an Parameter expression type
19                         Type: System.Int32, Name: a, ByRef: False 20         The Right argument is:
21                 This binary expression is a Add expression
22                 The Left argument is:
23                         This is an Constant expression type
24                         The type of the constant value is System.Int32 25                         The value of the constant value is 3
26                 The Right argument is:
27                         This is an Parameter expression type
28                         Type: System.Int32, Name: b, ByRef: False

請注意,括號不是輸出的一部分。 表達式樹中不存在表示輸入表達式中的括號的節點。 表達式樹的結構包含傳達優先級所需的全部信息。

今後示例擴展
此示例僅處理最基本的表達式樹。 在本部分中看到的代碼僅處理常量整數和二進制 + 運算符。 做爲最後一個示例,讓咱們更新訪問者以處理更加複雜的表達式。 讓咱們這樣來改進它:
Expression<Func<int, int>> factorial = (n) =>
    n == 0 
? 1
: Enumerable.Range(
1, n).Aggregate((product, factor) => product * factor);

此代碼表示數學 階乘 函數的一個可能的實現。 編寫此代碼的方式強調了經過將 lambda 表達式分配到表達式來生成表達式樹的兩個限制。 首先,lambda 語句是不容許的。 這意味着沒法使用循環、塊、if / else 語句和 C# 中經常使用的其餘控件結構。 我只能使用表達式。 其次,不能以遞歸方式調用同一表達式。 若是該表達式已經是一個委託,則能夠經過遞歸方式進行調用,但不能在其表達式樹的形式中調用它。 在有關生成表達式樹的部分中將介紹克服這些限制的技巧。

在此表達式中,將遇到全部這些類型的節點:

  1. Equal(二進制表達式)
  2. Multiply(二進制表達式)
  3. Conditional(? : 表達式)
  4. 方法調用表達式(調用 Range() 和 Aggregate()

修改訪問者算法的其中一個方法是持續執行它,並在每次到達 default 子句時編寫節點類型。 通過幾回迭代以後,便將看到每一個可能的節點。 這樣便萬事俱備了。 結果相似於:

 1 public static Visitor CreateFromExpression(Expression node)
 2 {
 3     switch(node.NodeType)
 4     {
 5         case ExpressionType.Constant:
 6             return new ConstantVisitor((ConstantExpression)node);
 7         case ExpressionType.Lambda:
 8             return new LambdaVisitor((LambdaExpression)node);
 9         case ExpressionType.Parameter:
10             return new ParameterVisitor((ParameterExpression)node);
11         case ExpressionType.Add:
12         case ExpressionType.Equal:
13         case ExpressionType.Multiply:
14             return new BinaryVisitor((BinaryExpression)node);
15         case ExpressionType.Conditional:
16             return new ConditionalVisitor((ConditionalExpression)node);
17         case ExpressionType.Call:
18             return new MethodCallVisitor((MethodCallExpression)node);
19         default:
20             Console.Error.WriteLine($"Node not processed yet: {node.NodeType}");
21             return default(Visitor);
22     }
23 }

ConditionalVisitor 和 MethodCallVisitor 將處理這兩個節點:

 1 public class ConditionalVisitor : Visitor
 2 {
 3     private readonly ConditionalExpression node;
 4     public ConditionalVisitor(ConditionalExpression node) : base(node)
 5     {
 6         this.node = node;
 7     }
 8 
 9     public override void Visit(string prefix)
10     {
11         Console.WriteLine($"{prefix}This expression is a {NodeType} expression");
12         var testVisitor = Visitor.CreateFromExpression(node.Test);
13         Console.WriteLine($"{prefix}The Test for this expression is:");
14         testVisitor.Visit(prefix + "\t");
15         var trueVisitor = Visitor.CreateFromExpression(node.IfTrue);
16         Console.WriteLine($"{prefix}The True clause for this expression is:");
17         trueVisitor.Visit(prefix + "\t");
18         var falseVisitor = Visitor.CreateFromExpression(node.IfFalse);
19         Console.WriteLine($"{prefix}The False clause for this expression is:");
20         falseVisitor.Visit(prefix + "\t");
21     }
22 }
23 
24 public class MethodCallVisitor : Visitor
25 {
26     private readonly MethodCallExpression node;
27     public MethodCallVisitor(MethodCallExpression node) : base(node)
28     {
29         this.node = node;
30     }
31 
32     public override void Visit(string prefix)
33     {
34         Console.WriteLine($"{prefix}This expression is a {NodeType} expression");
35         if (node.Object == null)
36             Console.WriteLine($"{prefix}This is a static method call");
37         else
38         {
39             Console.WriteLine($"{prefix}The receiver (this) is:");
40             var receiverVisitor = Visitor.CreateFromExpression(node.Object);
41             receiverVisitor.Visit(prefix + "\t");
42         }
43 
44         var methodInfo = node.Method;
45         Console.WriteLine($"{prefix}The method name is {methodInfo.DeclaringType}.{methodInfo.Name}");
46         // There is more here, like generic arguments, and so on.
47         Console.WriteLine($"{prefix}The Arguments are:");
48         foreach(var arg in node.Arguments)
49         {
50             var argVisitor = Visitor.CreateFromExpression(arg);
51             argVisitor.Visit(prefix + "\t");
52         }
53     }
54 }

且表達式樹的輸出爲:

 1 This expression is a/an Lambda expression type
 2 The name of the lambda is <null>
 3 The return type is System.Int32  4 The expression has 1 argument(s). They are:
 5         This is an Parameter expression type
 6         Type: System.Int32, Name: n, ByRef: False  7 The expression body is:
 8         This expression is a Conditional expression
 9         The Test for this expression is:
10                 This binary expression is a Equal expression
11                 The Left argument is:
12                         This is an Parameter expression type
13                         Type: System.Int32, Name: n, ByRef: False 14                 The Right argument is:
15                         This is an Constant expression type
16                         The type of the constant value is System.Int32 17                         The value of the constant value is 0
18         The True clause for this expression is:
19                 This is an Constant expression type
20                 The type of the constant value is System.Int32 21                 The value of the constant value is 1
22         The False clause for this expression is:
23                 This expression is a Call expression
24                 This is a static method call
25                 The method name is System.Linq.Enumerable.Aggregate 26                 The Arguments are:
27                         This expression is a Call expression
28                         This is a static method call
29                         The method name is System.Linq.Enumerable.Range 30                         The Arguments are:
31                                 This is an Constant expression type
32                                 The type of the constant value is System.Int32 33                                 The value of the constant value is 1
34                                 This is an Parameter expression type
35                                 Type: System.Int32, Name: n, ByRef: False 36                         This expression is a Lambda expression type
37                         The name of the lambda is <null>
38                         The return type is System.Int32 39                         The expression has 2 arguments. They are:
40                                 This is an Parameter expression type
41                                 Type: System.Int32, Name: product, ByRef: False 42                                 This is an Parameter expression type
43                                 Type: System.Int32, Name: factor, ByRef: False 44                         The expression body is:
45                                 This binary expression is a Multiply expression
46                                 The Left argument is:
47                                         This is an Parameter expression type
48                                         Type: System.Int32, Name: product, ByRef: False 49                                 The Right argument is:
50                                         This is an Parameter expression type
51                                         Type: System.Int32, Name: factor, ByRef: False
擴展現例庫

本部分中的示例演示訪問和檢查表達式樹中的節點的核心技術。 我略過了不少可能須要的操做,以便專一於訪問表達式樹中的節點這一核心任務。

首先,訪問者只處理整數常量。 常量值能夠是任何其餘數值類型,且 C# 語言支持這些類型之間的轉換和提高。 此代碼的更可靠版本可反映全部這些功能。

即便最後一個示例也只可識別可能的節點類型的一部分。 你仍能夠向其添加許多將致使其失敗的表達式。 完整的實現包含在名爲 ExpressionVisitor 的 .NET 標準中,且能夠處理全部可能的節點類型。

相關文章
相關標籤/搜索