.NET3.5新特性,Lambda表示式

mpsky發表於2021-09-09

隨VS 2005釋出的C#2.0引進了匿名方法的概念,允許在預期代理(delegate)值的地方用“行內(in-line)”程式碼塊(code blocks)來做替代。

Lambda表示式為編寫匿名方法提供了更簡明的函式式的句法,但結果卻在編寫LINQ查詢表示式時變得極其有用,因為它們提供了一個非常緊湊的而且類安全的方式來編寫可以當作引數來傳遞,在以後作運算的函式。

Lambda表示式的例子:

在我以前的擴充套件方法部落格貼子裡,我演示了你如何可以象下面這樣宣告一個簡單的Person類:

圖片描述

然後,我示範了你可以如何使用一些值來生成一個List集合的例項,然後使用由LINQ提供的新的Where和Average擴充套件方法來返回集合中的人的一個子集,以及計算這個集合中的人的平均年齡:

圖片描述

上面高亮標記的紅色 p => 表示式就是Lambda表示式。在上面的例子裡,我用第一個lambda來指定獲取特定人時所用的過濾條件,用第二個lambda來指定在計算平均年齡時該用Person物件的哪個值。

詳解Lambda表示式

理解Lambda表示式最容易的方法是把它們設想成編寫簡明的行內方法的方式。譬如,我上面編寫的例子可以使用C#2.0的匿名方法來編寫,象這樣:

圖片描述

上 面兩個匿名方法都接受一個Person型別的引數。第一個匿名方法返回一個布林值,表示Person的LastName是否是Guthrie,第二個匿名 方法返回一個整數值(返回那個人的年齡)。我們前面使用的lambda表示式的作用是一樣的,兩個表示式都接受一個Person型別的引數。第一個 lambda表示式返回一個布林值,第二個返回一個整數。

在C#裡,一個lambda表示式在句法上是寫成一個引數列表,隨後是 => 符號,隨後是表示式在呼叫時要運算的表示式或者語句塊:

params => expression

所以,當我們編寫這樣的lambda表示式時:

p => p.LastName == “Guthrie”

我們是想表示,我們在定義的Lambda接受一個引數p,要執行的程式碼表示式返回p.LastName的值是否等於“Guthrie”。 我們將引數命名為p是不相干的,我也可以很容易地將其命名為o,x,foo,或者我想要的任何名字。

不 象匿名方法要求引數型別是明確地指明的,Lambda表示式允許省略引數型別,而允許它們根據用法來推斷出型別。譬如,當我編寫 p=>p.LastName == “Guthrie” 這個lambda表示式時,編譯器推斷出p引數屬於Person型別,因為當前的Where擴充套件方法的物件是個範型的List集合。

Lambda引數的型別可以在編譯時和被Visual Studio的intellisense引擎推斷出來,這意味著在編寫lambda時你將獲得完全的intellisense 和編譯時檢查。譬如,注意當我在下面健入 p. 時,Visual Studio Orcas是如何提供intellisense完成的,因為它知道 p 是 Person型別:

圖片描述

注: 假如你要給一個Lambda表示式明確地宣告引數的型別的話,你可以在Lambda參數列裡的引數名字前宣告引數型別,象這樣:

圖片描述

針對框架開發人員的高階內容:Lambda表示式樹 (Lambda Expression Trees)

從 一個框架開發人員(framework developer)的角度來看,使得Lambda表示式特別強有力的事情之一是,它們既可以以基於IL的方法的形式被編譯成程式碼代理(code delegate),或者也可以編譯成一個表示式樹(expression tree)物件,然後在執行時用來分析,轉換或者最佳化表示式。

能將Lambda表示式編譯成一個表示式樹物件是個強大無比的機制,將促成許多使用場景,包括使用能提供編譯時句法檢查和VS intellisense的統一的查詢語言來建立支援豐富資料查詢的高效能物件對映器(無論是關聯式資料庫,活動目錄,還是web服務)之能力。

從Lambda表示式到程式碼代理 (Code Delegates)

上 面的Where擴充套件方法是個將Lambda表示式編譯成程式碼代理(code delegate)的例子(意即它是編譯成IL的,可以以代理的形式呼叫)。支援象上面那樣過濾任何IEnumerable集合的Where()擴充套件方法 可以使用下面這樣的擴充套件方法程式碼來實現:

圖片描述

上面的Where()擴充套件方法接受一個 Func 型別的過濾引數,該引數是個接受一個型別為T的引數,返回一個布林值表示條件是否滿足的方法之代理。當我們把Lambda表示式作為一個引數傳遞給這個 Where() 擴充套件方法時,C#編譯器會將我們的Lambda表示式編譯成IL方法代理(這裡, 將是Person),然後我們的Where()方法可以呼叫來計算某個給定條件是否被滿足了。

從Lambda表示式到表示式樹

當我們要想針對類似我們的列表集合一樣的記憶體中的資料做運算時,把lambda表示式編譯成程式碼代理是恰如其分的。但考慮一下你想要查詢資料庫裡的資料的情形(下面的程式碼是使用Orcas中內建的LINQ到SQL物件關係對映器寫成的) :

圖片描述

這裡,我要從資料庫裡取出一串強型別的Product物件,我向Where()擴充套件方法表示,要透過一個Lambda表示式來做過濾。

絕對不想 要看到發生的是,從資料庫裡取回所有的產品記錄,將它們放在一個區域性的集合裡,然後在記憶體裡對它執行Where()擴充套件方法來進行過濾。這麼做效率極其不 高,對大資料庫的擴縮性將是極差的。而我希望的是,LINQ到SQL的ORM將我上面的Lambda過濾條件翻譯成SQL表示式,然後在遠端的資料庫裡進 行過濾性查詢。那樣的話,我只返回那些符合查詢條件的記錄,這樣的資料庫查詢效率是非常高的。

框架開發人員可以透過宣告他們的Lambda表示式引數是個Expression型別,而不是Func型別來取得這樣的結果。這會導致Lambda表示式引數被編譯成一個我們可以在執行時拆開和分析的表示式樹:

圖片描述

注意上面我是怎麼把我們在先前用過的同樣的 p=>p.LastName == “Guthrie” Lambda表示式,但這次將其賦值給一個 Expression> 變數,而不是Func 變數。編譯器不會產生IL,而是會指派一個表示式樹物件,然後我作為一個框架開發人員就可以用它來對相應的Lambda表示式進行分析,按我想要的方式對其進行運算(譬如,我可以挑出表示式中的型別,名字和值等)。

在LINQ到SQL的情形下,它會將這個Lambda過濾語句翻譯成標準的關係SQL語句,來對資料庫進行操作(從邏輯上來說,一個“SELECT * from Products where UnitPrice

IQueryable 介面

為 幫助框架開發人員建立可查詢的資料提供器,LINQ提供了 IQueryable 介面。這個介面實現了標準的LINQ擴充套件方法查詢運算子,提供了一個更便利的方式來實現對一個複雜的表示式樹的處理(譬如,象下面這樣,我用了3個不同的 擴充套件方法,2個lambda來從資料庫取回10個產品的情形):

圖片描述


結語

希 望上面的貼子內容對如何考慮和使用Lambda表示式提供了基本的理解。當與Orcas中System.Linq名稱空間下提供的內建標準查詢擴充套件方法結 合使用時,它們提供了一個非常好的方式來對任何型別的資料進行查詢和互動,同時還保持了對完整的編譯時檢查和intellisense的支援。

通 過利用由Lambda提供的對錶達式樹的支援,以及 IQueryable 介面,構建資料提供器的框架開發人員可以確保開發人員編寫的乾淨的編碼,對任何資料來源(無論是資料庫,XML檔案,記憶體中的物件,web服務,LDAP系 統等)執行起來速度快而且效率高。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/1806/viewspace-2801228/,如需轉載,請註明出處,否則將追究法律責任。

相關文章