隐约记得 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 中实验成功