作為委托的Lambda表達式
從很多方面看, Lambda表達式可以被看成是C# 2中匿名方法的進化. 幾乎沒有什麼匿名方法能做而Lambda表達式不能做的事情, 而Lambda表達式幾乎總是能提供更好的可讀性且更簡潔. 特別的, lambda表達式和匿名方法捕獲變量的行為是完全一致的. 在兩者多數的顯式行為當中並沒有存在太多的不同——不過lambda表達式擁有大量的快捷方式, 這在很多常規情況下使得代碼更加簡潔. 與匿名方法一樣, lambda表達式也擁有自己特殊的轉換規則——表達式本身的類似並不是一個委托類型, 但其可以通過多種方法被隱式或顯式轉換成為一個委托實例. 術語匿名函數(anonymous function)覆蓋了匿名方法和lambda表達式——因為在很多案例中兩者都可以應用相同的轉換規則.
接下來我們將從一個簡單的例子開始, 首先我們將使用匿名方法. 我們將會創建一個委托實例, 其帶有一個string類型的參數並且返回int作為結果(計算string的長度). 首先我們選擇使用委托類型. 幸運的是.NET 3.5擁有一系列的泛型委托類型可以幫助我們.
Func<…>委托類型
.NET 3.5的System命名空間下有5個泛型Func委托類型. Func並沒有什麼特別之處——僅僅是讓你方便的擁有了一些預定義的泛型委托, 它們可以適用於多種情況. 每一個委托簽名分別擁有0到4個參數, 其類型就是參數本身被賦予的類型, 最後一個參數則是返回值的類型. 以下是所有Func委托類型的簽名:
1: public delegate TResult Func<TResult>()
2: public delegate TResult Func<T,TResult>(T arg)
3: public delegate TResult Func<T1,T2,TResult>(T1 arg1, T2 arg2)
4: public delegate TResult Func<T1,T2,T3,TResult>
5: (T1 arg1, T2 arg2, T3 arg3)
6: public delegate TResult Func<T1,T2,T3,T4,TResult>
7: (T1 arg1, T2 arg2, T3 arg3, T4 arg4)
例如, Func<string, double, int>等價於一個來自於下面代碼的委托類型:
1: delegate int SomeDelegate(string arg1, double arg2)
Action<…>提供了等價的功能, 唯一的區別就是其沒有任何的返回值. 單一單數的Action存在於.NET 2.0中, 其他的全部都是.NET 3.5中新引入的。因為我們的例子將會擁有一個string參數, 並且返回int類型, 因此我們將使用Func<string,int>.
首次轉換到lambda表達式
現在我們知道了委托類型, 我們可以首先使用匿名方法創建委托實例, 如下所示:
1: Func<string,int> returnLength;
2: returnLength = delegate (string text) { return text.Length; };
3: Console.WriteLine (returnLength("Hello"));
執行結果控制台打印出”5″, 與我們期望的一樣, 接下來讓我們嘗試將它轉換為lambda表達式. 最冗長的lambda表達式格式為:
(explicitly-typed-parameter-list) => { statements }
=>是C# 3中新引入的, 其告訴編譯器我們正在使用lambda表達式. 多數時候, lambda表達式會和一個帶有nonvoid返回類型的委托一起使用. 在C# 1中, 委托通常是被事件使用而很少返回值, 雖然lambda表達式也可以同樣適用, 但更多時候當它們需要返回值時用lambda表達式看起來將更加高雅.
下面的代碼與前面的例子結果一致, 只不過是用的lambda表達式:
1: Func<string,int> returnLength;
2: returnLength = (string text) => { return text.Length; };
3: Console.WriteLine (returnLength("Hello"));
當讀到lambda表達式的時候, 我們可以將=>想像成為”goes to”, 因此上述代碼可以被理解成text goes to text.length. 在匿名方法中給定的規則同樣適用於lambda表達式: 你不能從一個lambda表達式中嘗試返回一個void的返回類型. 目前為止我們並沒有節省太多的空間而且讓代碼及其容易的閱讀, 現在讓我們開始使用一些快捷方式.
使用單一表達式作為主體
目前為止我們使用了一整塊的代碼去返回值, 這是非常靈活的, 因為我們可以使用多行語句, 執行循環, 在代碼塊內部的多個地方返回值等等, 與匿名方法一樣. 然而很多時候我們只需要一個單一的表達式就可以, 它的執行值就是整個lambda表達式的值. 在這些案例中, 我們可以僅僅指定表達式本身, 沒有任何的大括號, 返回語句和分號. 其格式為:(explicitly-typed-parameter-list) => expression
在我們的例子中, 這意味著我們的lambda表達式變為:
1: (string text) => text.Length
現在, 這看起來已經開始變得簡單, 那麼, 參數類型呢? 編譯器已經知道Func<string, int>需要的是一個string的參數, 因此我們能夠僅僅命名該參數…
隱式類型的參數列表
多數時候, 即使你沒有顯式指明, 編譯器依然可以猜測參數類型. 在這些案例中, 我們可以將lambda表達式寫成這樣:
(implicitly-typed-parameter-list) => expression
一個隱式類型的參數列表就是一個用逗號分開的名稱列表, 你不能將兩種不同方式組成的方式混合在一起——要嗎他們都是隱式的, 要嗎他們都是顯式的. 另外, 如果有任何的參數是out或者ref類型的參數, 你只能使用顯式類型參數. 在我們的例子中沒有什麼問題, 因此我們的lambda表達式變為:
1: (text) => text.Length
現在代碼已經變得非常簡短——已經沒有什麼太多東西我們可以進一步剔除的了. 雖然參數看起來有一點冗長.
單一參數的快捷方式
當lambda表達式僅僅需要一個參數, 並且該參數可以是隱式類型, C# 3允許我們省略圓括號, 如下:
parameter-name => expression
因此我們的表達式最後將變成:
1: text => text.Length
你可以會好奇為什麼lambda表達式會有這麼多特殊的規則——例如, 語言的其他部分並不會去關心方法擁有一個參數或多個參數. 然後, 這看起來似乎很特別的例子實際上是及其常見的, 而且從參數列表當中移除圓括號對於提高可讀性是很有意義的, 特別是在一小段代碼當中擁有多個lambda表達式的時候. 值得一提的是你還是可以像其他表達式那樣使用圓括號, 如果你確實想這麼做的話. 有時候, 當你想把lambda表達式賦值給一個變量或者屬性的時候, 這對於提高可讀性是有幫助的——否則, 等號可能會讓人有點混淆, 下面的代碼使用了這種做法:
1: Func<string,int> returnLength;
2: returnLength = (text => text.Length);
3: Console.WriteLine (returnLength("Hello"));
一開始你可能會覺得上面的代碼有一點點難以閱讀, 這與匿名方法第一次出現的時候是一樣的, 直到開發人員開始習慣使用它們. 當你開始習慣使用lambda表達式的時候, 你一定會感激它們是多麼的簡潔方便. 難以想象還有更短更清晰的方法可以用於創建委托實例. 我們當然可以將變量名從text改為x, 實際上, 在LINQ當中, 這是很常用的, 然而, 更長的名字給閱讀人員帶來了更多的信息.