Lambda表達式

Lambda表達式

匿名函數
Lambda表達式(lambdaexpression)是一個匿名函數,Lambda表達式基于數學中的λ演算得名,直接對應于其中的lambda抽象(lambdaabstraction),是一個匿名函數,即沒有函數名的函數。Lambda表達式可以表示閉包(注意和數學傳統意義上的不同)。
  • 中文名:Lambda表達式
  • 外文名:
  • 别名:
  • 表達式:
  • 提出者:
  • 适用領域:
  • 英文名:Lambda expression
  • 名稱起源:λ演算
  • 學科:程序設計

定義

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,其中Func是包含至多五個輸入參數的任何Func委托。同樣,Lambda表達式隻是一種用于構造表達式目錄樹的非常簡練的方式。盡管事實上通過Lambda創建的對象的類型是不同的,但Lambda使得Where調用看起來類似。

在前面的示例中,請注意委托簽名具有一個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(names,delegate(stringv){returnv.StartsWith("a");});

string[]findNameB=Array.FindAll(names,v=>v.StartsWith("a"));

上面中兩個FindAll方法的反編譯代碼如下:

string[]findNameA=Array.FindAll(names,delegate(stringv){returnv.StartsWith("a");});

string[]findNameB=Array.FindAll(names,delegate(stringv){returnv.StartsWith("a");});

從而可以知道“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(Tx,Ty);

staticvoidPrint(AddHandlertest)

{

Console.WriteLine("inttype:{0}",test(1,2));

}

staticvoidPrint(AddHandlertest)

{

Console.WriteLine("doubletype:{0}",test(1d,2d));

}

staticvoidMain()

{

Print((x,y)=>x+y);

Console.Read();

}

}

當我們編譯以下代碼時,編譯器将會顯示以下錯誤信息:

在以下方法或屬性之間的調用不明确:

“ConsoleApplication1.Test.Print(ConsoleApplication1.Test.AddHandler)”和“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必須使用尾置返回來指定返回類型。

例如調用中的std::sort,ISO C++ 98 的寫法是要先寫一個compare函數:

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

相關詞條

相關搜索

其它詞條