隱約記得 Lambda 表達式來源於 C# 5.0,但又不太肯定,因而查了下 百度百科:Lambda表達式,仍然沒有獲得明確的答案,因此懶得去糾結這個問題了。javascript
C# 有一種比較特殊的語言特性,叫「委託」。在 C#2.0 之前,只能使用命名方法來聲明委託。C#2.0 引入了匿名方法。而在 C#3.0 中,引入 Lambda 表達式取代了匿名方法。因爲 C#3.0 同時引入了 Linq 語法,因此能夠認爲 Lambda 表達式是爲簡化 Linq 寫法而生的,雖然它並不只僅用於 Linq。java
C# 中 Lambda 表達式的運算符是 =>
,這個個符號的左側是參數,右側是表達式或語句塊。因此 Lambda 表達式的主要形式是這樣node
(參數列表) => { 語句塊 }
當「語句塊」只有一條語句的時候,能夠省略大括號,就成了程序員
(參數列表) => 語句
但這裏「語句」不包括 「return 語句」。由於 return
後面必定是一個表達式。這種狀況下,應該直接寫表達式,省略掉 return
express
(參數列表) => 表達式
若是想加上
return
,就要把大括號加上編程(參數列表) => { return 表達式; }
通常狀況下,若是 =>
右邊是語句,都會寫成語句塊的形式,因此 (參數列表) => 語句
這種形式是不多用的。那麼經常使用的就是閉包
(參數列表) => 表達式
(參數列表) => { 語句塊 }
以上各類形式統稱 Lambda 表達式,但在 Resharper 中,上述經常使用的兩種分別被稱爲 lambda expression 和 lambda statement。因此若是偶爾聽到說 Lambda 語句,也不要吃驚。函數
因爲 Lambda 表達式通常是做爲參數或者值使用,因此根據使用的上下文,大部分狀況下編譯器能夠推斷出 Lambda 表達式的參數類型。正由於如此,Lambda 表達式的參數一般是省略類型的。好比this
Func<decimal, string> format = (d) => d.ToString("N2");
上面的例子中,編譯器會推導出 d
是 decimal
類型,因此能夠調用帶格式參數的 ToString
(若是沒有推導出類型,而是把 d
看成 object
,其 ToString
是不能帶參數的,就會出現編譯錯誤);另外,返回類型 string
也很容易根據 ToString()
的結果推導出來,若是把類型改成 Func<decimal, int>
就會出現編譯錯誤。.net
Lambda 表達式的目的之一就是簡潔,因此,當只有一個參數的時候,能夠省略參數列表兩端的括號,那麼上面的示例能夠寫成
Func<decimal, string> format = d => d.ToString("N2");
另外,在某些時候,編譯器不能推導出參數類型,那就須要爲參數指定類型,在這種狀況下,雖然只有一個參數,括號也不能省略了。好比上面的例子能夠改爲
Func<decimal, string> format = (decimal d) => d.ToString("N2");
最後還有一種狀況,編譯器不能推導出返回值類型,那就須要進行類型轉換處理,好比
Func<string> test = () => ((object) 123) as string;
這個例子除了說明語法以外,毫無心義。實際應用中有可能須要將某個
object
類型的引用轉換成指定類型返回,就應該使用相似的語法。
Lambda 表達式最經常使用於委託。Linq 中大量使用委託,因此在寫 Linq 的時候,會大量使用 Lambda。好比從 Person 列表中找出年齡小於20的
class Person { public string Name { get;set; } public int Age { get; set; } }
Person[] FindYounger(IEnumerable<Person> persons) { // linq 寫法 // return (from p in persons // where p.Age < 20 // select p).ToArray(); // 方法鏈寫法 return persons.Where(p => p.Age < 20).ToArray(); }
linq 寫法更像是 SQL,不過方法鏈寫法的 Where()
方法調用很明顯的使用了 Lambda 表達式做爲參數。
來看看 Where()
擴展方法的定義
public static IEnumerable<TSource> Where<TSource>( this IEnumerable<TSource> source, Func<TSource, bool> predicate )
除 this
參數以外,用於判斷去留的參數是 Func<TSource, bool>
,這是在 System
命名空間中定義的大量 Action
和 Func
系列泛型委託中的一個。因此本身在使用委託的時候,也能夠利用這些系統預約義的公共委託,免得本身再去定義。
題外話
Action
和Func
系列委託是 .NET 3.5 加入的。在這以前不少人須要無參數無返回值委託的時候,都偷懶使用System.Threading.ThreadStart
。如今能夠換成System.Action
了,雖然其本質是同樣的,可是不利於人工理解。
事件是委託的一種特殊應用。在沒有 Lambda 以前,習慣使用私有命名方法來寫事件。有了 Lambda 就能夠大大簡化了。
button.Click += (sender, args) => { MessageBox.Show("你點了我!"); }
不過因爲 Visual Studio 在設置界面的時候會自動幫咱們生成事件函數,因此,其實這種應用要相對少得很,畢竟本身寫事件裝載的人仍是很少。
Lambda 表達式原本就是用來代替匿名方法的,因此能夠用匿名方法的地方均可以用 Lambda。一個比較典型的狀況就是(尤爲是對 JavaScript 程序員來講),在某個方法中想臨時定義一個方法來專項處理某些事情的時候,就可使用 Lambda 表達式。固然,不能直接使用,須要使用 Action
或 Func
包裝。
string GetFormatedAmount() { decimal price = GetPrice(); decimal number = GetNumber(); return new Func<decimal>(() => { return decimal.Round(price * number, 2); }).Invoke().ToString("N2"); }
這個例子一樣只是爲了示例,實際意義不大。可是從這個例子中能夠發現 Lambda 的一個特色——閉包。請注意到,這裏 Lambda 表達式中用於計算的變量都是局部變量,而非經過參數傳入。換個更明顯的例子
Func<decimal> GetAlgorithm(int type) { decimal price = GetPrice(); decimal number = GetNumber(); switch (type) { case 1: // 8折 return () => { return decimal.Round(price * number * 0.8m, 2); }; case 2: // 滿10贈1 return () => { var payNumber = number > 10m ? number - 1 : number; return decimal.Round(price * payNumber, 2); }; default: // 無優惠 return () => { return decimal.Round(price * number, 2); }; } } void Calc() { var algorithm = GetAlgorithm(GetType()); var amount = algorithm(); Console.WriteLine(amount); }
Java8 中增長了 Lambda 表達式語法,與 C# 不一樣,運算符是用的 ->
而不是 =>
,也許這會讓 C++ 轉 Java 的程序員抓狂,不過對於純粹的 Java 程序員來講,就是一個符號而已。
不過剛纔說了,Lambda 表達式的做用其實就是匿名方法,而 Java 中並無匿名方法這一語法。不過 Java 中有匿名對象,當你直接 new
一個接口並實現接口方法的時候,Java 編譯器實際是產生了一個類(匿名類)來實現這個接口,而後再返回這個類的一個實例,也就是匿名對象。
ActionListner listener = new ActionListener() { public void actionPerformed(ActionEvenet e) { System.out.println("Hello, anonymous object"); } };
就上面這個例子,若是提取其關鍵語法去匹配 Lambda 的語法定義,很容易就變成了
// 僅抽象語法,不能編譯經過 (e) -> { System.out.println("Hello, anonymous object"); }
問題在於,Java 的 lambda 必定是某個接口的實例,因此它必須有目標類型。上例中單純的 Lambda 是 Java 編譯器不能編譯的,由於沒有目標類型,不知道該生成一個什麼類型的對象。因此正確的寫法應該是
ActionListener listener = e -> { System.out.println("hello anonymouse object"); };
若是目標變量類型不明確的時候,須要申明其類型
Object listener = (ActionListener) e -> { System.out.println("hello anonymouse object"); };
關於 Java 的 Lambda,這裏提到的只是皮毛,推薦你們看看這篇博客:Java8 Lambda 表達式教程
ES6 爲 JavaScript 加入了 Lambda 表達式的新語法。不過在 JavaScript 中不叫 Lambda,叫箭頭操做符,使用的和 C# 同樣是 =>
。
基於對於 JavaScript 來講,Lambda 做用並不大,由於 JavaScript 的函數就是對象,定義自由,使用也很自由。不過箭頭操做符仍然帶來了你們喜歡它的理由……解決了 this
指針混淆的問題。仍是來看例子
var person = { name: "James", friends: ["Jack", "Lenda", "Alpha" ], shakeAll: function() { this.friends.forEach(function(friend) { console.log(`${this.name} shake with ${friend}`); }); } } person.shakeAll();
這個沒有 箭頭操做符的例子,看起來沒有什麼不對,可是運行出來卻跟預期的不同,由於在 forEach
中的 function
用錯了 this
。因此 shakeAll
應該改爲
shakeAll: function() { var _this = this; this.friends.forEach(function(friend) { console.log(`${_this.name} shake with ${friend}`); }); }
可是若是用箭頭操做符,就不用擔憂這個問題了
shakeAll: function() { this.friends.forEach(friend => { console.log(`${this.name} shake with ${friend}`); }); }
然而這個特性在不少 JavaScript 引擎中都還沒實現,至少 Chromium 42.0.2311 沒實現,io.js 3.2 沒實現。不過在 Firefox 40 中試驗成功。
補充:在 node.js 4.0 中實驗成功