定义
lambda 表达式,又称匿名函数,常用来表示内部仅包含 1 行表达式的函数。如果一个函数的函数体仅有 1 行表达式,则该函数就可以用 lambda 表达式来代替。
C#表达式
C#的Lambda表达式都使用Lambda运算符=>,该运算符读为“goesto”。语法如下:
形参列表=>函数体
函数体多于一条语句的可用大括号括起。
类型
可以将此表达式分配给委托类型,如下所示:
delegateintdel(inti);
delmyDelegate=x=>{returnx*x;};
intj=myDelegate(5);//j=25
创建表达式目录树类型:
usingSystem.Linq.Expressions;
//...
Expression=x=>x*x;
=>运算符具有与赋值运算符(=)相同的优先级,并且是右结合运算符。
Lambda用在基于方法的LINQ查询中,作为诸如Where和Where等标准查询运算符方法的参数。
使用基于方法的语法在Enumerable类中调用Where方法时(像在LINQtoObjects和LINQtoXML中那样),参数是委托类型System..::.Func<(Of<(T,TResult>)>)。使用Lambda表达式创建委托最为方便。例如,当您在System.Linq..::.Queryable类中调用相同的方法时(像在LINQtoSQL中那样),则参数类型是System.Linq.Expressions..::.Expression
在前面的示例中,请注意委托签名具有一个int类型的隐式类型输入参数,并返回int。可以将Lambda表达式转换为该类型的委托,因为该表达式也具有一个输入参数(x),以及一个编译器可隐式转换为int类型的返回值。(以下几节中将对类型推理进行详细讨论。)使用输入参数5调用委托时,它将返回结果25。
在is或as运算符的左侧不允许使用Lambda。
适用于匿名方法的所有限制也适用于Lambda表达式。有关更多信息,请参见匿名方法(C#编程指南)。
特殊
下列规则适用于Lambda表达式中的变量范围:
捕获的变量将不会被作为垃圾回收,直至引用变量的委托超出范围为止。
在外部方法中看不到Lambda表达式内引入的变量。
Lambda表达式无法从封闭方法中直接捕获ref或out 参数。
Lambda表达式中的返回语句不会导致封闭方法返回。
Lambda表达式不能包含其目标位于所包含匿名函数主体外部或内部的goto语句、break语句或continue语句。
Lambda表达式的本质是“匿名方法”,即当编译程序代码时,“编译器”会自动将“Lambda表达式”转换为“匿名方法”,如下例:
string[]names={"agen","balen","coure","apple"};
string[]findNameA=Array.FindAll
string[]findNameB=Array.FindAll
上面中两个FindAll方法的反编译代码如下:
string[]findNameA=Array.FindAll
string[]findNameB=Array.FindAll
从而可以知道“Lambda表达式”与“匿名方法”是可以划上等号的,只不过使用“Lambda表达式”输写代码看上去更直观漂亮,不是吗?
Lambda表达式的语法格式:
参数列=>语句或语句块
其中“参数列”中可包含任意个参数(与委托对应),如果参数列中有0个或1个以上参数,则必须使用括号括住参数列,如下:
()=>Console.Write("0个参数")
I=>Console.Write("1个参数时参数列中可省略括号,值为:{0}",i)
(x,y)=>Console.Write("包含2个参数,值为:{0}*{1}",x,y)
而“语句或语句块”中如果只有一条语句,则可以不用大括号括住否则则必须使用,如下:
I=>Console.Write("只有一条语句")
I=>{Console.Write("使用大括号的表达式");}
//两条语句时必须要大括号
I=>{i++;Console.Write("两条语句的情况");}
如果“语句或语句块”有返回值时,如果只有一条语句则可以不输写“return”语句,编译器会自动处理,否则必须加上,如下示例:
classTest
{
delegateintAddHandler(intx,inty);
staticvoidPrint(AddHandleradd)
{
Console.Write(add(1,3));
}
staticvoidMain()
{
Print((x,y)=>x+y);
Print((x,y)=>{intv=x*10;returny+v;};
Console.Read();
}
}
“Lambda表达式”是委托的实现方法,所以必须遵循以下规则:
1)“Lambda表达式”的参数数量必须和“委托”的参数数量相同;
2)如果“委托”的参数中包括有ref或out修饰符,则“Lambda表达式”的参数列中也必须包括有修饰符;
例子:
classTest
{
delegatevoidOutHandler(outintx);
staticvoidPrint(OutHandlertest)
{
inti;
test(outi);
Console.Write(i);
}
staticvoidMain()
{
Print((outintx)=>x=3);
Console.Read();
}
}
注: 如果包括有修饰符,则“Lambda表达式”中的参数列中也必须加上参数的类型
3)如果“委托”有返回类型,则“Lambda表达式”的“语句或语句块”中也必须返回相同类型的数据;
4)如果“委托”有几种数据类型格式而在“Lambda表达式”中“编译器”无法推断具体数据类型时,则必须手动明确数据类型。
例子:
(错误代码)
classTest
{
delegateTAddHandler
staticvoidPrint(AddHandler
{
Console.WriteLine("inttype:{0}",test(1,2));
}
staticvoidPrint(AddHandler
{
Console.WriteLine("doubletype:{0}",test(1d,2d));
}
staticvoidMain()
{
Print((x,y)=>x+y);
Console.Read();
}
}
当我们编译以下代码时,编译器将会显示以下错误信息:
在以下方法或属性之间的调用不明确:
“ConsoleApplication1.Test.Print(ConsoleApplication1.Test.AddHandler
所以我们必须明确数据类型给编译器,如下:
1Print((intx,inty)=>x+y);
这样我们的代码就能编译通过了。
Java表达式
Java8的一个大亮点是引入Lambda表达式,使用它设计的代码会更加简洁。当开发者在编写Lambda表达式时,也会随之被编译成一个函数式接口。下面这个例子就是使用Lambda语法来代替匿名的内部类,代码不仅简洁,而且还可读。
没有使用Lambda的老方法:
1button.addActionListener(new ActionListener(){
2public void actionPerformed(ActionEvent ae){
3System.out.println("Actiondetected");
4}
5});
使用Lambda:
1button.addActionListener(()->{
2System.out.println("Actiondetected");
3});
让我们来看一个更明显的例子。
不采用Lambda的老方法:
1Runnable runnable1=new Runnable(){
2@Override
3public void run(){
4System.out.println("RunningwithoutLambda");
5}
6};
使用Lambda:
1button.addActionListener(()->{
2System.out.println("Actiondetected");
3});
来看一个更明显的例子。
不采用Lambda的老方法:
1Runnablerunnable1=new Runnable(){
2@Override
3publicvoid run(){
4System.out.println("RunningwithoutLambda");
5}
6};
使用Lambda:
1Runnablerunnable2=()->{
2System.out.println("RunningfromLambda");
3};
正如所看到的,使用Lambda表达式不仅让代码变的简单、而且可读、最重要的是代码量也随之减少很多。然而,在某种程度上,这些功能在Scala等这些JVM语言里已经被广泛使用。
并不奇怪,Scala社区是难以置信的,因为许多Java 8里的内容看起来就像是从Scala里搬过来的。在某种程度上,Java 8的语法要比Scala的更详细但不是很清晰,但这并不能说明什么,如果可以,它可能会像Scala那样构建Lambda表达式。
一方面,如果Java继续围绕Lambda来发展和实现Scala都已经实现的功能,那么可能就不需要Scala了。另一方面,如果它只提供一些核心的功能,例如帮助匿名内部类,那么Scala和其他语言将会继续茁壮成长,并且有可能会凌驾于Java之上。其实这才是最好的结果,有竞争才有进步,其它语言继续发展和成长,并且无需担心是否会过时。
C++表达式
ISOC++ 11 标准的一大亮点是引入Lambda表达式。基本语法如下:
[capture list] (parameter list) ->return type { function body }
其中除了“[ ]”(其中捕获列表可以为空)和“复合语句”(相当于具名函数定义的函数体),其它都是可选的。它的类型是唯一的具有成员operator()的非联合的类类型,称为闭包类型(closure type)。
C++中,一个lambda表达式表示一个可调用的代码单元。可以将其理解为一个未命名的内联函数。它与普通函数不同的是,lambda必须使用尾置返回来指定返回类型。
例如调用
1boolcompare(int&a,int&b)
2{
3returna>b;//降序排序
4}
然后,再这样调用:
1sort(a,a+n,compare);
然而,用ISO C++ 11 标准新增的Lambda表达式,可以这么写:
1sort(a,a+n,[](int a,int b){return a>b;});//降序排序
这样一来,代码明显简洁多了。
由于Lambda的类型是唯一的,不能通过类型名来显式声明对应的对象,但可以利用auto关键字和类型推导:
1autof=[](int a,int b){return a>b;};
和其它语言的一个较明显的区别是Lambda和C++的类型系统结合使用,如:
1autof=[x](int a,int b){return a>x;});//x被捕获复制
2intx=0,y=1;
3autog=[&](int x){return ++y;});//y被捕获引用,调用g后会修改y,需要注意y的生存期
4bool(*fp)(int,int)=[](int a,int b){return a>b;});//不捕获时才可转换为函数指针
Lambda表达式可以嵌套使用。
即将出版的ISO C++14支持基于类型推断的泛型lambda表达式。上面的排序代码可以这样写:
1sort(a,a+n,[](const auto&a,const auto&b){return a>b;});//降序排序:不依赖a和b的具体类型
因为参数类型和函数模板参数一样可以被推导而无需和具体参数类型耦合,有利于重构代码;和使用auto声明变量的作用类似,它也允许避免书写过于复杂的参数类型。特别地,不需要显式指出参数类型使使用高阶函数变得更加容易。
Python表达式
Lambda表达式是Python中一类特殊的定义函数的形式,使用它可以定义一个匿名函数。与其它语言不同,Python的Lambda表达式的函数体只能有唯一的一条语句,也就是返回值表达式语句。其语法如下:
lambda形参列表:函数返回值表达式语句
下面是个Lambda表达式的例子:
1#!/usr/bin/envpython
2li=[{"age":20,"name":"def"},{"age":25,"name":"abc"},{"age":10,"name":"ghi"}]
3li=sorted(li,key=lambda x:x["age"])
4print(li)
如果不用Lambda表达式,而要写成常规的函数,那么需要这么写:
1#!/usr/bin/envpython
2defcomp(x):
3returnx["age"]
4li=[{"age":20,"name":"def"},{"age":25,"name":"abc"},{"age":10,"name":"ghi"}]
5li=sorted(li,key=comp)
6print(li)
函数语言
Lambda演算和函数式语言的计算模型天生较为接近,Lambda表达式一般是这些语言必备的基本特性。如:
Scheme:
1(lambda(x)(+x1))
Haskell:
1x->x+1