C#3.0新增功能10 表達式樹 07 翻譯(轉換)表達式

  本篇將介紹如何訪問表達式樹中的每一個節點,同時生成該表達式樹的已修改副本。 如下是在兩個重要方案中將使用的技巧。 第一種是瞭解表達式樹表示的算法,以即可以將其轉換到另外一個環境中。 第二種是什麼時候更改已建立的算法。 這多是爲了添加日誌記錄、攔截方法調用並跟蹤它們,或其餘目的。html

轉換即訪問

  生成的用於轉換表達式樹的代碼是你已看到的用於訪問樹中全部節點的代碼的擴展。 轉換表達式樹時,會訪問全部節點,並在訪問它們的同時生成新樹。 新樹可包含對原始節點的引用或已放置在樹中的新節點。node

讓咱們經過訪問表達式樹,並建立具備一些替換節點的新樹,來查看其工做原理。 在此示例中,咱們將任何常數替換爲其十倍大的常數。  咱們經過將常數節點替換爲執行乘法運算的新節點來進行此替換,而沒必要閱讀常數的值並將其替換爲新的常數。算法

此處,在找到常數節點後,建立新乘法節點(其子節點是原始常數和常數 10):安全

private static Expression ReplaceNodes(Expression original)
{
    if (original.NodeType == ExpressionType.Constant)
    {
        return Expression.Multiply(original, Expression.Constant(10));
    }
    else if (original.NodeType == ExpressionType.Add)
    {
        var binaryExpression = (BinaryExpression)original;
        return Expression.Add(ReplaceNodes(binaryExpression.Left),
                              ReplaceNodes(binaryExpression.Right));
    }
    return original;
}

經過替換原始節點,將造成一個包含修改的新樹。 能夠經過編譯並執行替換的樹對此進行驗證。數據結構

var one = Expression.Constant(1, typeof(int));
var two = Expression.Constant(2, typeof(int));
var addition = Expression.Add(one, two);
var sum = ReplaceNodes(addition);
var executableFunc = Expression.Lambda(sum);

var func = (Func<int>)executableFunc.Compile();
var answer = func();
Console.WriteLine(answer);

生成新樹是二者的結合:訪問現有樹中的節點,和建立新節點並將其插入樹中。框架

此示例演示了表達式樹不可變這一點的重要性。 請注意,上面建立的新樹混合了新建立的節點和現有樹中的節點。 這是安全的,由於現有樹中的節點沒法進行修改。 這能夠極大提升內存效率。 相同的節點可能會在整個樹或多個表達式樹中遍歷使用。 因爲不能修改節點,所以能夠在須要時隨時重用相同的節點。async

遍歷並執行加法
  經過生成遍歷加法節點的樹並計算結果的第二個訪問者來對此進行驗證。 能夠經過對目前見到的訪問者進行一些修改來執行此操做。 在此新版本中,訪問者將返回到目前爲止加法運算的部分總和。 對於常數表達式,該總和即爲常數表達式的值。 對於加法表達式,遍歷這些樹後,其結果爲左操做數和右操做數的總和。
 
var one = Expression.Constant(1, typeof(int));
var two = Expression.Constant(2, typeof(int));
var three= Expression.Constant(3, typeof(int));
var four = Expression.Constant(4, typeof(int));
var addition = Expression.Add(one, two);
var add2 = Expression.Add(three, four);
var sum = Expression.Add(addition, add2);

// 聲明委託,這樣就能夠從它自己遞歸地調用它
Func<Expression, int> aggregate = null;
// 聚合、返回常量或左、右操做數之和。
// 主要簡化:假設每一個二進制表達式都是一個加法。
aggregate = (exp) => exp.NodeType == ExpressionType.Constant 
? (int)((ConstantExpression)exp).Value
: aggregate(((BinaryExpression)exp).Left)
+ aggregate(((BinaryExpression)exp).Right); var theSum = aggregate(sum); Console.WriteLine(theSum);

此處有至關多的代碼,但這些概念是很是容易理解的。 此代碼訪問首次深度搜索後的子級。 當它遇到常數節點時,訪問者將返回該常數的值。 訪問者訪問這兩個子級以後,這些子級將計算出爲該子樹計算的總和。 加法節點如今能夠計算其總和。 在訪問了表達式樹中的全部節點後,將計算出總和。 能夠經過在調試器中運行示例並跟蹤執行來跟蹤執行。工具

讓咱們經過遍歷樹,來更輕鬆地跟蹤如何分析節點以及如何計算總和。 下面是包含大量跟蹤信息的聚合方法的更新版本:this

private static int Aggregate(Expression exp)
{
    if (exp.NodeType == ExpressionType.Constant)
    {
        var constantExp = (ConstantExpression)exp;
        Console.Error.WriteLine($"Found Constant: {constantExp.Value}");
        return (int)constantExp.Value;
    }
    else if (exp.NodeType == ExpressionType.Add)
    {
        var addExp = (BinaryExpression)exp;
        Console.Error.WriteLine("Found Addition Expression");
        Console.Error.WriteLine("Computing Left node");
        var leftOperand = Aggregate(addExp.Left);
        Console.Error.WriteLine($"Left is: {leftOperand}");
        Console.Error.WriteLine("Computing Right node");
        var rightOperand = Aggregate(addExp.Right);
        Console.Error.WriteLine($"Right is: {rightOperand}");
        var sum = leftOperand + rightOperand;
        Console.Error.WriteLine($"Computed sum: {sum}");
        return sum;
    }
    else throw new NotSupportedException("Haven't written this yet");
}

在同一表達式中運行該版本將生成如下輸出:spa

10
Found Addition Expression
Computing Left node
Found Addition Expression
Computing Left node
Found Constant: 1
Left is: 1
Computing Right node
Found Constant: 2
Right is: 2
Computed sum: 3
Left is: 3
Computing Right node
Found Addition Expression
Computing Left node
Found Constant: 3
Left is: 3
Computing Right node
Found Constant: 4
Right is: 4
Computed sum: 7
Right is: 7
Computed sum: 10
10

跟蹤輸出,並在上面的代碼中跟隨。 應當可以看出代碼如何在遍歷樹的同時訪問代碼和計算總和,並得出總和。

如今,讓咱們來看看另外一個運行,其表達式由 sum1 給出:

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

下面是經過檢查此表達式獲得的輸出:

Found Addition Expression
Computing Left node
Found Constant: 1
Left is: 1
Computing Right node
Found Addition Expression
Computing Left node
Found Constant: 2
Left is: 2
Computing Right node
Found Addition Expression
Computing Left node
Found Constant: 3
Left is: 3
Computing Right node
Found Constant: 4
Right is: 4
Computed sum: 7
Right is: 7
Computed sum: 9
Right is: 9
Computed sum: 10
10

雖然最終結果是相同的,但樹遍歷徹底不一樣。 節點的訪問順序不一樣,由於樹是以首先發生的不一樣運算構造的。

限制

存在一些很差翻譯成表達式樹的較新的 C# 語言元素。 表達式樹不能包含 await 表達式或 async lambda 表達式。 C# 6 發行中添加的許多功能不會徹底按照表達式樹中所編寫的那樣顯示。 較新的功能可能會顯示在表達式樹中等效、早期的語法中。 這可能不像你想象的那樣有侷限性。 實際上,這意味着在引入新語言功能時,解釋表達式樹的代碼將仍可能照常運行。

即便具備這些限制,經過表達式樹,仍可建立依賴於解釋和修改表示爲數據結構的代碼的動態算法。 它是一種功能強大的工具,做爲 .NET 生態系統的一種功能,它可以使豐富的庫(如實體框架)完成其所執行的操做。

相關文章
相關標籤/搜索