1. 程式人生 > 實用技巧 >由淺入深表達式樹(一)建立表示式樹

由淺入深表達式樹(一)建立表示式樹

由淺入深表達式樹(一)建立表示式樹

  為什麼要學習表示式樹?表示式樹是將我們原來可以直接由程式碼編寫的邏輯以表示式的方式儲存在樹狀的結構裡,從而可以在執行時去解析這個樹,然後執行,實現動態的編輯和執行程式碼。LINQ to SQL就是通過把表示式樹翻譯成SQL來實現的,所以瞭解表達樹有助於我們更好的理解 LINQ to SQL,同時如果你有興趣,可以用它創造出很多有意思的東西來。

  表示式樹是隨著.NET 3.5推出的,所以現在也不算什麼新技術了。但是不知道多少人是對它理解的很透徹, 在上一篇Lambda表示式的回覆中就看的出大家對Lambda表示式和表示式樹還是比較感興趣的,那我們就來好好的看一看這個造就了LINQ to SQL以及讓LINQ to Everything的好東西吧。

  本系列計劃三篇,第一篇主要介紹表示式樹的建立方式。第二篇主要介紹表示式樹的遍歷問題。第三篇,將利用表示式樹打造一個自己的LinqProvider。

  本文主要內容:

  • 由Lambda表示式建立簡單的表示式樹
  • 手動建立複雜的表示式樹
  • 表示式樹型別列表及示例

建立一個簡單的Lambda表示式樹

  在 上一篇Lambda表示式中我們提到了可以直接根據Lambda表示式來建立表示式樹,這應該是最直接的建立表示式樹的方式了。

Expression<Func<int, int>> expr = x => x + 1;
Console.WriteLine(expr.ToString());  // x=> (x + 1)

// 下面的程式碼編譯不通過
Expression<Func<int, int, int>> expr2 = (x, y) => { return x + y; };
Expression<Action<int>> expr3 = x => {  };

  但是別想象的太美好,這種方式只能建立最簡單的表示式樹,複雜點的編譯器就不認識了。

  右邊是一個Lambda表示式,而左邊是一個表示式樹。為什麼可以直接賦值呢?這個就要多虧我們的Expression<TDelegate>泛型類了。而Expression<TDelegate>是直接繼承自LambdaExpression的,我們來看一下Expression的建構函式:

internal Expression(Expression body, string name, bool tailCall, ReadOnlyCollection<ParameterExpression> parameters)
    : base(typeof(TDelegate), name, body, tailCall, parameters)
{
}

  實際上這個建構函式什麼也沒有做,只是把相關的引數傳給了父類,也就是LambdaExpression,由它把我們表示式的主體,名稱,以及引數儲存著。

Expression<Func<int, int>> expr = x => x + 1;
Console.WriteLine(expr.ToString());  // x=> (x + 1)

var lambdaExpr = expr as LambdaExpression;
Console.WriteLine(lambdaExpr.Body);   // (x + 1)
Console.WriteLine(lambdaExpr.ReturnType.ToString());  // System.Int32

foreach (var parameter in lambdaExpr.Parameters)
{
    Console.WriteLine("Name:{0}, Type:{1}, ",parameter.Name,parameter.Type.ToString());
}

//Name:x, Type:System.Int32

  簡單的來說,Expression<TDelegate>泛型類做了一層封裝,方便我們根據Lambda表示式來建立Lambda表示式樹。它們中間有一個轉換過程,而這個轉換的過程就發生在我們編譯的時候。還記得我們Lambda表示式中講的麼?Lambda表示式在編譯之後是普通的方法,而Lambda式樹是以一種樹的結構被載入到我們的執行時的,只有這樣我們才可以在執行時去遍歷這個樹。但是為什麼我們不能根據Expression<TDelegate>來建立比較複雜的表示式樹呢?您請接著往下看。

建立一個複雜的Lambda表示式樹

  下面我們就來一步一步的建立一個複雜的表示式樹,你們準備好了麼?上面我們講到直接由Lambda表示式的方式來建立表示式樹,可惜只限於一種型別。下面我們就來演示一下如何建立一個無參無返回值的表示式樹。

// 下面的方法編譯不能過 
/*
Expression<Action> lambdaExpression2 = () =>
{
    for (int i = 1; i <= 10; i++)
    {
        Console.WriteLine("Hello");
    }
};
*/     
       
// 建立 loop表示式體來包含我們想要執行的程式碼
LoopExpression loop = Expression.Loop(
    Expression.Call(
        null,
        typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }),
        Expression.Constant("Hello"))
        );

// 建立一個程式碼塊表示式包含我們上面建立的loop表示式
BlockExpression block = Expression.Block(loop);

// 將我們上面的程式碼塊表示式
Expression<Action> lambdaExpression =  Expression.Lambda<Action>(block);
lambdaExpression.Compile().Invoke();

  上面我們通過手動編碼的方式建立了一個無參的Action,執行了一組迴圈。程式碼很簡單,重要的是我們要熟悉這些各種型別的表示式以及他們的使用方式。上面我們引入了以下型別的表示式:

  看起來神密的表示式樹也不過如此嘛?如果大家去執行上面的程式碼,就會陷入死迴圈,我沒有為loop加入break的條件。為了方便大家理解,我是真的一步一步來啊,現在我們就來終止這個迴圈。就像上面那一段不能編譯通過的程式碼實現的功能一樣,我們要輸出10個”Hello”。

  上面我們先寫了一個LoopExpression,然後把它傳給了BlockExpresson,從而形成的的一塊程式碼或者我們也可以說一個方法體。但是如果我們有多個執行塊,而且這多個執行塊裡面需要處理同一個引數,我們就得在block裡面宣告這些引數了。

ParameterExpression number=Expression.Parameter(typeof(int),"number");
            
BlockExpression myBlock = Expression.Block(
    new[] { number },
    Expression.Assign(number, Expression.Constant(2)),
    Expression.AddAssign(number, Expression.Constant(6)),
    Expression.DivideAssign(number, Expression.Constant(2)));

Expression<Func<int>> myAction = Expression.Lambda<Func<int>>(myBlock);
Console.WriteLine(myAction.Compile()());
// 4

  我們聲明瞭一個int的變數並賦值為2,然後加上6最後除以2。如果我們要用變數,就必須在block的你外面宣告它,並且在block裡面把它引入進來。否則在該表示式樹時會出現,變數不在作用域裡的錯。

  下面我們繼續我們未完成的工作,為迴圈加入退出條件。為了讓大家快速的理解loop的退出機制,我們先來看一段虛擬碼:

LabelTarget labelBreak = Expression.Label();
Expression.Loop(
    "如果 條件 成功"
        "執行成功的程式碼"
    "否則"
        Expression.Break(labelBreak) //跳出迴圈
    , labelBreak); 

  我們需要藉助於LabelTarget 以及Expression.Break來達到退出迴圈的目地。下面我們來看一下真實的程式碼:

LabelTarget labelBreak = Expression.Label();
ParameterExpression loopIndex = Expression.Parameter(typeof(int), "index");

BlockExpression block = Expression.Block(
new[] { loopIndex },
// 初始化loopIndex =1 
    Expression.Assign(loopIndex, Expression.Constant(1)),
    Expression.Loop(
        Expression.IfThenElse(
            // if 的判斷邏輯
            Expression.LessThanOrEqual(loopIndex, Expression.Constant(10)),
            // 判斷邏輯通過的程式碼
            Expression.Block(
                Expression.Call(
                    null,
                    typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }),
                    Expression.Constant("Hello")),
                Expression.PostIncrementAssign(loopIndex)),
            // 判斷不通過的程式碼
            Expression.Break(labelBreak)
            ),labelBreak));

// 將我們上面的程式碼塊表示式
Expression<Action> lambdaExpression =  Expression.Lambda<Action>(block);
lambdaExpression.Compile().Invoke();

  希望上面的程式碼沒有阻止你學習表示式樹的決心J 。

  好吧,我們又學了幾個新的型別的表示式,來總結一下:

  到這裡,我想大家應該對錶達式樹的構建有了一個清楚的認識。至於為什麼不允許我們直接基於複雜的Lambda表示式來建立表示式樹呢?

  • 這裡的Lambda表示式實際上是一個Expression Body。
  • 這個Expression Body實際上就是我們上面講到的Expression中的一種。
  • 也就是說編譯器需要時間去分析你到底是哪一種?
  • 最簡單的x=> x+1之類的也就是Func<TValue,TKey> 是很容易分析的。
  • 實際這裡面允許的Expression Body只有BinaryExpression。

最後,我們來完整的看一下.NET都為我們提供了哪些型別的表示式(下面這些類都是繼承自Expression)。

TypeBinaryExpression

TypeBinaryExpression typeBinaryExpression =
    Expression.TypeIs(
        Expression.Constant("spruce"),
        typeof(int));

Console.WriteLine(typeBinaryExpression.ToString());
// ("spruce" Is Int32)

IndexExpression

ParameterExpression arrayExpr = Expression.Parameter(typeof(int[]), "Array");

ParameterExpression indexExpr = Expression.Parameter(typeof(int), "Index");

ParameterExpression valueExpr = Expression.Parameter(typeof(int), "Value");

Expression arrayAccessExpr = Expression.ArrayAccess(
    arrayExpr,
    indexExpr
);

Expression<Func<int[], int, int, int>> lambdaExpr = Expression.Lambda<Func<int[], int, int, int>>(
        Expression.Assign(arrayAccessExpr, Expression.Add(arrayAccessExpr, valueExpr)),
        arrayExpr,
        indexExpr,
        valueExpr
    );

Console.WriteLine(arrayAccessExpr.ToString());
// Array[Index]

Console.WriteLine(lambdaExpr.ToString());
// (Array, Index, Value) => (Array[Index] = (Array[Index] + Value)) 

Console.WriteLine(lambdaExpr.Compile().Invoke(new int[] { 10, 20, 30 }, 0, 5));
// 15

NewExpression

NewExpression newDictionaryExpression =Expression.New(typeof(Dictionary<int, string>));
Console.WriteLine(newDictionaryExpression.ToString());
// new Dictionary`2()

InvocationExpression

Expression<Func<int, int, bool>> largeSumTest =
    (num1, num2) => (num1 + num2) > 1000;

InvocationExpression invocationExpression= Expression.Invoke(
    largeSumTest,
    Expression.Constant(539),
    Expression.Constant(281));

Console.WriteLine(invocationExpression.ToString());
// Invoke((num1, num2) => ((num1 + num2) > 1000),539,281)

  今天我們演示瞭如何通過程式碼的方式去建立表示式樹,然後總結了一下.NET為我們提供的表示式型別。下一篇,我們將繼續研究表示式樹的遍歷問題,敬請期待,如果對於表示式樹有興趣的同學歡迎持續關注~,