將C# lambda表達式轉換成表達式樹
就像我們已經看到的, lambda表達式可以隱式或顯式的被轉換為適當的委托實例. 然而, 這並非唯一可用的轉換規則, 你也可以讓編譯器幫你從一個lambda表達式來構建表達式樹, 然後在執行時創建一個Expression<TDelegate>實例. 例如, 下面的例子使用了更簡短的方式創建”return 5″的表達式, 然後編譯並執行結果委托:
1: Expression<Func<int>> return5 = () => 5;
2: Func<int> compiled = return5.Compile();
3: Console.WriteLine(compiled());
在第一行代碼中, ()=>5是lambda表達式, 在這個例子中, 如果我們用圓括號將其包圍會讓其看起來更糟糕也不是更好. 注意我們並不需要任何的類型轉換, 編譯器可以幫助我們完成所有的東西. 你可以編寫2+3代替5, 然而編譯器會幫我們優化(編譯後的代碼直接保存的就是5). 很重要的一點就是, lambda表達式已經被轉換為表達式樹.
限制——並非所有的lambda表達式都可以被轉換成為表達式樹. 你不能將包含一整塊語句(甚至是只有一個return語句)的表達式轉換為表達式樹——只能評估一個單一表達式. 表達式不能包含指派(assignments), 因為它們不能以表達式樹的方式展現. 盡管這就是最常規的限制, 不過這並不是唯一——完全清單不值得再這裡討論, 因為這些問題極少碰到. 而且如果你試圖嘗試這種非法轉換, 編譯器會及時幫你發現的.
讓我們再來看看一個更復雜的例子, 尤其是當引入參數的時候. 這次我們將編寫一個斷言來判斷兩個輸入參數中, 第一個是否是可以從第二個參數開始的. 使用lambda表達式的依然還是很簡單:
1: Expression<Func<string,string,bool>> expression =
2: ( (x,y) => x.StartsWith(y) );
3: var compiled = expression.Compile();
4: Console.WriteLine(compiled("First", "Second"));
5: Console.WriteLine(compiled("First", "Fir"));
使用表達式樹本身的話就要復雜多了:
1: MethodInfo method = typeof(string).GetMethod("StartsWith", new[] { typeof(string) });
2: var target = Expression.Parameter(typeof(string), "x");
3: var methodArg = Expression.Parameter(typeof(string), "y");
4: Expression[] methodArgs = new[] { methodArg };
5: Expression call = Expression.Call(target, method, methodArgs);
6: var lambdaParameters = new[] { target, methodArg };
7: var lambda = Expression.Lambda<Func<string,string,bool>>(call, lambdaParameters);
8: var compiled = lambda.Compile();
9: Console.WriteLine(compiled("First", "Second"));
10: Console.WriteLine(compiled("First", "Fir"));
盡管上面的代碼看起來更加的復雜, 不過其更清晰的展現了表達式樹上有什麼, 以及參數是如何被綁定的. 讓我們從方法調用開始, 其構成了表達式的主體: 調用方法的目標, (換句話說, 調用StartsWith的字符串); 方法本身(作為MethodInfo); 參數列表(在這個例子中, 只有一個參數). 碰巧我們的例子我們方法調用目標和參數都是做為parameters傳入到表達式當中的, 不過它們可以是其它的表達式類型——常量, 方法調用值, 屬性值等等.
在將方法調用構建成表達式後, 我們需要將其轉換成為lambda表達式, 綁定我們需要的參數. 我們重用了為方法調用而創建的相同的ParameterExpression值: 他們在lambda表達式中被指定的順序就是最後調用委托它們被選取的順序.
看看上面創建復雜的代碼,僅僅是一個簡單的方法調用, 可以想象如果是更復雜一點的表達式結果將會是如何——應該感到高興的是, C# 3可以通過lambda表達式創建表達式樹. 有一個小問題可以稍微提一下, C# 3 編譯器通過類似上面的方式來創建表達式樹, 不過它擁有一個快捷方式: 編譯器並不會使用反射去獲取String.StartsWith的MethodInfo, 相反的, 它使用與方法等價的typeof操作符, 這僅僅是在IL代碼當中可用, C#本身並不支持, 而且從方法群組創建委托實例的時候也應用了相同的操作符. 現在我們看到了表達式樹和lambda表達式是如何聯系在一起的, 讓我們來簡短的看一下為什麼它們如此有用.
LINQ的心髒——表達式樹
如果沒有lambda表達式, 表達式樹只能有相對小得多的價值. 它僅僅是CodeDOM的另外一個選擇, 尤其是當你只想建模一個單一表達式而不是完整的語句, 方法, 類型等等, 而且帶來的好處相當有限.
反過來說也一樣, 沒有表達式樹, lambda表達式用處也將少得多. 擁有一種更簡潔的方式創建委托實例依然會是很受歡迎的, 一種更加函數式化的開發方式也還是可行的. 而當與擴展方法一起使用的時候, lambda表達式尤其有效. 然而, 當與表達式樹一起的時候, 事情變得更加有趣.
那麼當我們把lambda表達式, 表達式樹和擴展方法組合在一起的時候得到了什麼呢? 答案是LINQ. 在很長的一段時間內我們已經能夠擁有很棒的編譯時檢查, 我們也可以告訴另外一個平台讓其去運行一些代碼, 它們通常是明文的(例如SQL 查詢), 但我們從來沒有能夠在同一時間完成它們. lambda表達式提供了編譯時檢查, 而表達式樹提供了運行時的抽象, 將它們捆綁在一起, 我們擁有了兩個世界. LINQ provider可以從我們熟悉的編程語言中(例如C#)產生表達式樹並作為中間格式, 其可以被轉換為目標平台的本地語言, 例如SQL. LINQ to SQL provider就讓我們可以使用C#來產生SQL語句.