在很早以前就聽說過表達式樹了,但並沒有去了解它。雖然自己用過linq to sql和linq to entity,但也就用着就用着,並沒有去深究c#代碼怎么會生成sql代碼而不是IL。廢話不多說了,開寫吧!
.net里表達式樹核心概念就是:將代碼作為數據。它將一些代碼表示為一個對象樹,樹中的每個節點本身都是一個表達式,不同的表達式類型代表能在代碼中執行不同操作:二元操作,一元操作,方法調用等等。
System.Linq.Expressions命名空間包含了代表表達式的各個類。所有的表達式類都從Expression類派生,Expression是個抽象類,主要包含的是一些靜態的方法,這些方法用於生成其他表達式類的實例。Expression類還包含了兩個重要屬性:
1.Type屬性:代表了表達式求值結果的類型。比如,一個表達式是要獲取一個字符串的Length屬性,那么該表達式的Type屬性應為int類型
2.NodeType屬性:代表了表達式的種類。這個種類表示成ExpressionType枚舉的一個成員:LessThan,Invoke,Multiply,MemberAccess等等(有80幾種,汗!)。
一個表達式樹的簡單例子
static void Main(string[] args) { Expression firstArg = Expression.Constant(2); Expression secondArg = Expression.Constant(4); Expression add = Expression.Add(firstArg, secondArg); Console.WriteLine(add); }
輸出結果:
上面的代碼將會生成如下圖的表達式樹:

值得注意的是,表達式中“葉”表達式在代碼中是最先創建的:表達式是自下而上構建的。這是由“表達式不易變”這一事實實現的。
將表達式樹編譯成委托
LambdaExpression是從Expression派生的類型。泛型類Expression<TDelegate>是從LambdaExpression派生的,其中泛型參數TDelegate必須是委托類型。
LambdaExpression有個Compile方法能創建恰當類型的一個委托。而Expression<TDelegate>的Compile方法返回TDelegate類型的委托。來看看下面的例子:
static void Main(string[] args) { Expression firstArg = Expression.Constant(2); Expression secondArg = Expression.Constant(4); Expression add = Expression.Add(firstArg, secondArg); Expression<Func<int>> func = Expression.Lambda<Func<int>>(add); Func<int> compiled = func.Compile(); Console.WriteLine(compiled()); }
我們通過Expression.Lambda<TDelegate>(Expression expression)方法來創建Expression<TDelegate>類型對象,再調用其Compile方法獲取表達式樹編譯出的委托實例。
將C# Lambda表達式轉換成表達式樹
我們知道Lambda表達式能顯示或隱式地轉換成恰當的委托實例。但是,編譯器也能很輕松的將Lambda表達式構建為一個表達式樹:
//將Lambda表達式轉換成表達式樹 Expression<Func<int>> return5 = () => 5;
但是,並不是所有的Lambda表達式都能轉換成表達式樹,有一些限制:不能將帶有一個語句塊的Lambda轉換成一個表達式樹-----只有對單個表達式進行求值得Lambda才可以。表達式中不能包含賦值操作,因為表達式樹中表示不了這種操作。還有其他一些較少見的限制,總而言之,如果存在轉換問題,你會在編譯時發現。
位於Linq核心的表達式樹
表達式樹可以說是Linq的核心之一,為什么是Linq的核心之一呢?因為表達式樹使得c#不再是僅僅能編譯成IL,我們可以通過c#生成一個表達式樹,將結果作為一個中間格式,在將其轉換成目標平台上的本機語言。比如SQL。我們常用的Linq to sql就是這樣生成SQL的。
下圖展示了Linq to Objects和Linq to SQL的不同路徑:

資料參考於《深入理解c#》第二版
