標籤:line 有一個 服務 res 重用 member 環境 簡單 color
Lambda運算式和運算式樹狀架構
先放一張委託轉換的進化圖
看一看到lambda簡化了委託的使用。
lambda可以隱式的轉換成委託或者運算式樹狀架構。轉換成委託的話如下面的代碼:
Func<string, int> getLength = s => s.Length;
轉換成運算式樹狀架構的話是下面的代碼:
Expression<Func<string, int>> getLength = s => s.Length;
委託方面的東西前面都做了詳細的介紹。我們主要學習運算式樹狀架構
運算式樹狀架構
運算式是當今程式設計語言中最重要的組成成分。簡單的說,運算式就是變數、數值、運算子、函數組合起來,表示一定意義的式子。例如下面這些都是(C#)的運算式:
3 //常數運算式a //變數或參數運算式!a //一元邏輯非運算式a + b //二元加法運算式Math.Sin(a) //方法調用運算式new StringBuilder() //new 運算式
myString.length//MemberAccess運算式
首先澄清一個概念:運算式。運算式是一個以;結尾的句子,如果是兩句,那就不叫運算式了,比如下面不是運算式:
{....}
代碼作為資料是一個古老的概念,.NET3. 5的運算式樹狀架構提供了一種抽象的方式將一些代碼錶示成一個對象樹。 它類似於CodeDOM, 但是在一個稍高的層級上操作。 運算式樹狀架構主要用於LINQ, 本節稍後會解釋運算式樹狀架構對於整個LINQ的重要性。
System.Linq.Expressions命名空間包含了代表運算式的各個類, 它們都繼承自Expression,一個抽象的主要包含一些靜態Factory 方法的類, 這些方法用於建立其他運算式類的執行個體。 然而,Expression 類也包括兩個屬性。
- Type屬性代表運算式求值後的.NET類型, 可把它 視為一個傳回型別。 例如,如果一個運算式要擷取一個字串的Length屬性, 該運算式的類型就是int。
- NodeType屬性返回所代表的運算式的種類。 它是ExpressionType枚舉的成員, 包括LessThan、Multiply和Invoke等。 仍然使用上面的例子,對於myString. Length 這個屬性訪問來說, 其節點類型是MemberAccess。
static void Main(string[] args) { Expression firstArg = Expression.Constant(2); Expression secondArg = Expression.Constant(3); BinaryExpression add = Expression.Add(firstArg, secondArg); Console.WriteLine(add); Console.ReadKey(); }
上面的結果最終會輸出(2+3).這意味著這些運算式樹狀架構類覆蓋了ToString來產生可讀的輸出。
就像代碼中所做的,首先建立的是葉運算式,然後自下而上的去建立這個完整的運算式。這是由“ 運算式不易變” 這一事實決定的——建立好運算式後, 它就永遠不會改變。 這樣就可以隨心所欲地緩衝和重用運算式。
LambdaExpression是從Expression派生的類型之一。 泛型類Expression<TDelegate>又是從LambdaExpression派生的。
Expression和Expression<TDelegate> 類的區別在於, 泛型類以靜態類型的方式標識了它是什麼種類的運算式,也就是說,它確定了傳回型別和參數。 很明顯, 這是用TDelegate型別參數來表示的, 它必須是一個委託類型。 例如, 假設我們的簡單加法運算式就是一個 不擷取任何參數, 並返回整數的委託。 與之匹配的簽名就是Func<int>, 所以可以使用一個Expression <Func<int>>, 以靜態類型的方式表示該運算式。 我們用Expression.Lambda 方法來完成這件事。 該方法有許多重載版本——我們的例子使用的是泛型方法, 它用一個型別參數來指定我們想要表示的委託的類型。
static void Main(string[] args) { Expression firstArg = Expression.Constant(2); Expression secondArg = Expression.Constant(3); BinaryExpression add = Expression.Add(firstArg, secondArg); Func<int> lambda = Expression.Lambda<Func<int>>(add).Compile(); Console.WriteLine(lambda()); Console.ReadKey(); }
Expression.Lambda有泛型的重載,指示運算式可以轉換為一個類型實參為Func<int>的Expression,然後,可以通過Compile方法來將運算式樹狀架構編譯成委託。
印象。我們在程式中建立了一些邏輯塊(比如Expression firstArg = Expression.Constant(2);), 將其表示成普通對象, 然後要求架構將所有的東西都編譯成可以執行的“真實” 的代碼。 你或許永遠都不需要真正以這種方式使用運算式樹狀架構, 甚至永遠都不需要在程式中構造 它們,但它提供了相當有用的背景知識,可以協助你理解LINQ是怎樣工作的。
上面介紹的是將運算式樹狀架構編譯成lambda,下面介紹的是----
將lambda轉換成運算式樹狀架構
我們知道,Lambda運算式能顯式或隱式地轉換成恰當的委託執行個體。 然而, 這並非唯一能進行的轉換。 還可以要求編譯器通過你的Lambda 運算式構建一個運算式樹狀架構, 在執行時建立Expression<TDelegate> 的一個執行個體。 例如,下面展示了用一種精簡得多的方式 建立“返回 5” 的運算式, 然後編譯這個運算式, 並調用編譯得到的委託。
static void Main(string[] args) { Expression<Func<int>> return5 = () => 5; Func<int> lambda = return5.Compile(); Console.WriteLine(lambda()); Console.ReadKey(); }
一些限制:
- 並非**所有** Lambda運算式都能轉換成運算式樹狀架構。 不能將帶有一個語句塊(即使只有一個return語句) 的Lambda 轉換成運算式樹狀架構—— 只有對單個運算式進行求值的Lambda才可以。
- 運算式中還不能包含賦值操作,因為在運算式樹狀架構中表示不了這種操作。 儘管.NET4 擴充了運算式樹狀架構的功能, 但只能轉換單一運算式這一限制仍然有效。
- 還有一些其他的限制,不過很少見,你會在編譯錯誤的時候得到編譯器的提示
更複雜的例子:
static void Main(string[] args) { Expression<Func<string, string, bool>> expression = (x, y) => x.StartsWith(y); var lambda = expression.Compile(); Console.WriteLine(lambda("first that i did","first"));//true Console.ReadKey(); }
上面這個例子如果用運算式樹狀架構來做的話是非常複雜的:
static void Main(string[] args) { MethodInfo method = typeof(string).GetMethod("StartsWith", new []{typeof(string)});//①構造這個方法調用的各個組件 var target = Expression.Parameter(typeof(string), "x"); var methodArg = Expression.Parameter(typeof(string), "y"); Expression[] methodArgs =new[] {methodArg}; Expression call = Expression.Call(target, method, methodArgs);//②從以上組件構建callexpression var lambdaParameters = new[]{target, methodArg};//③將callexpression轉化成lambda var lambda = Expression.Lambda<Func<string,string,bool>>(call,lambdaParameters); var compiled = lambda.Compile(); Console.WriteLine(compiled("first","second"));//false Console.WriteLine(compiled("first", "fir"));//true Console.ReadKey(); }
首先感謝編譯器能夠讓lambda隱式轉化成運算式樹狀架構!
唯一的好處是它確實更清晰地展示樹中涉及的東西以及參數是如何綁定的。 為了構造最終的方法調用運算式, 我們需要知道方法調用的幾個組件①, 其中包括: 方法的目標(也就是調用StartsWith的字串);方法本身( MethodInfo);參數列表(本例只有一個參數)。在本例中, 方法的目標和參數恰好都是傳遞給運算式的參數, 但它們完全可能是其他運算式類型, 如常量、其他方法調用的結果、屬性的求值結果, 等等。 將方法調用構造成一個運算式之後 ?, 接著需要把它轉換成Lambda運算式 ?, 並綁定參數。 我們重用 了作為方法調用(組件)資訊而建立的參數運算式的值(ParameterExpression): 建立Lambda運算式時指定的參數順序就是最終調用委託時使用的參數順序。
這是編譯好之後的運算式樹狀架構。
位於linq核心的運算式樹狀架構
沒有lambda運算式,運算式樹狀架構幾乎沒有任何價值。從一定程度上說,沒有運算式樹狀架構,lambda也就沒那麼有用了。LINQ在C#的全部體現就包括lambda、運算式樹狀架構和擴充方法這三部分。
長期以來, 我們要麼能在編譯時間進行很好的檢查, 要麼能指示另一個平台運行一些代碼, 這些指示一般表示成文本(如SQL查詢)。 但是,魚和熊掌不可兼得(這個卻是lambda的優點,既能轉化成委託,在進程內配合迭代器進行處理集合序列,又能編譯成運算式樹狀架構,以供其他提供器翻譯成另一個平台上的語言,比如sql)。 Lambda運算式提供 了編譯時間檢查的能力, 而運算式樹狀架構可以將執行模型從你所需的邏輯中提取出來。 將兩者合并到一起之後, 魚和熊掌就能兼得 了—— 當然是在一個合理的範疇之內。“ 進程外” LINQ提供器的中心思想在於, 我們可以從一個熟悉的源語言(如 C#) 產生一個運算式樹狀架構, 將結果作為一個中間格式, 再將其轉換成目標平台上的本地語言, 比如SQL。 某些時候, 你更多地會遇到一個本機API, 而不是一種簡單的母語。 例如, 這個API可能根據運算式所表示的 內容來調用不同的Web服務。展示LINQ to Objects 和 LINQ to SQL 的 不同 路徑。
除了LINQ,運算式樹狀架構也可以用在別的地方
1、我們在以後的內容中討論C#動態類型 時, 將看到更多關於動態語言運行時的內容。 運算式樹狀架構是其架構的核心部分。 它們具有三個特點對DLR特別有吸引力:
- 它們是不易變的, 因此可以安全地緩衝;
- 它們是可組合的, 因此可以在簡單的塊中構建出複雜的行為;
- 它們 可以編譯為委託, 後者可以像平常那樣進一步JIT 編譯為本地代碼。
2、可以放心地對成員的引用進行重構
以後C#會推出一個infoof的操作符,但具體是幹啥的還不知道,這裡先做標記。以後來補充。
3、其他。。。。
這個回頭再來看一下書上的介紹吧。貌似沒有用到過
類型推斷和重載決策的改變
C#3中lambda運算式的加入使得原先的類型推斷和重載決策為了新的環境而做了改變。
C#複習筆記(4)--C#3:革新寫代碼的方式(Lambda運算式和運算式樹狀架構)