文章出處

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

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

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

  本文主要內容:

創建一個簡單的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為我們提供的表達式類型。下一篇,我們將繼續研究表達式樹的遍歷問題,敬請期待,如果對于表達式樹有興趣的同學歡迎持續關注~,


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()