標籤:c# lambda 委託 匿名函數
匿名函數
匿名函數(Anonymous Function)是表示“內聯”方法定義的運算式。匿名函數本身及其內部沒有值或者類型,但是可以轉換為相容的委託或者運算式樹狀架構類型。匿名函數轉換的計算取決於轉換的目標類型:如果是委託類型,則轉換計算為引用匿名函數所定義的方法的委託;如果是運算式樹狀架構類型,則轉換將計算以對象結構形式表示方法結構的運算式樹狀架構。
匿名函數有兩種文法風格:Lambda運算式(lambda-expression)和匿名方法運算式(anonymous-method-expression)。在幾乎所有的情況下,Lambda運算式都比匿名方法運算式更為簡介具有表現力。但現在C#語言中仍保留了後者,為了向後相容。
Lambda運算式:
async可選 (匿名的函數簽名)=> (匿名的函數體)
匿名方法運算式:
async可選 delegate (顯式的匿名函數簽名) 可選{代碼塊}
其中匿名的函數簽名可以包括兩種,一種是隱式的匿名函數簽名另一種是顯式的匿名函數簽名:
隱式的函數簽名:(p)、(p1,p1)
顯式的函數簽名:(int p)、(int p1,int p2)、(ref int p1,out int p2)
匿名的函數體可以是運算式或者代碼塊。
從上面我們可以看出,Lambda運算式的參數形式可以顯式或者隱式類型化。在顯式類型化參數列表中,每個參數的類型是顯式聲明的,在隱式類型化參數列表中,參數的類型是從匿名函數出現的上下文中推斷出來的。
當Lambda運算式只有一個具有隱式類型化參數的時候,參數列表可以省略圓括弧,也就是說:
(參數) => 運算式
可以簡寫為
參數 => 運算式
一些匿名函數的樣本
x => x + 1 //隱式的類型化,函數體為運算式
x => {return x + 1;} //隱式的類型化,函數體為代碼塊
(int x) => x + 1 //顯式的類型化,函數體為運算式
(int x) => {return x + 1;} //顯式的類型化,函數體為代碼塊
(x , y) => x * y //多參數
() => Console.WriteLine() //無參數
async (t1 , t2) => await t1 + await t2 //非同步
delegate (int x) {return x + 1;} //匿名函數方法運算式
delegate {return 1 + 1;} //參數列表省略
Lambda運算式和匿名方法運算式的區別:
● 當沒有參數的時候,匿名方法運算式允許完全省略參數列表,從而可以轉換為具有任意值參數列表的委託類型,Lambda運算式則不能省略參數列表的圓括弧()。
● Lambda運算式允許省略和推斷型別參數,而匿名方法運算式要求顯式聲明參數類型。
● Lambda運算式主體可以為運算式或者代碼塊,而匿名方法運算式的主體必須為代碼塊。
● 只有Lambda運算式可以相容到運算式樹狀架構類型。
委託
一個委託是一個指向一個方法的引用,或者說,一個委託的執行個體就是一個指向某個方法的對象,這是一個簡單卻十分強大的概念。
C#中的委託是用來處理在其他語言中(如C++、Pascal等)需要用函數指標來處理的情況。不過與C++不同的是:委託是完全物件導向的;C++指標僅僅指向成員函數,而委託同時封裝了對象的執行個體和方法;委託是完全型別安全的,只有當函數的簽名與委託的簽名匹配的時候,委託才可以指向該方法,當委託沒有合法的指向方法的時候不能被調用。
一些關於委託的知識點:
1.委託是型別安全的
委託類型的傳回型別必須為void或者輸出安全,委託類型的所有形參類型都必須是輸入安全的。
2.委託類型是名稱等效,不是結構等效
也就是說,對於兩個委託類型,即使它們具有相同的參數列表和傳回型別,它們仍將被視為兩個不同的委託類型。
例如:
delegate int A(int x);
delegate int B(int x);
A和B是兩個不同的委託。
但是,兩個結構一樣的委託,它們的執行個體可以指向同一個方法。
3.委託的調用列表(多播委託)
委託執行個體所封裝的方法集合稱為調用列表。
當我們從某個方法建立一個委託執行個體的時候,該執行個體將封裝此方法,該執行個體中的調用列表包含一個“進入點”。當我們組合多個非空的委託執行個體的時候,它們的調用列表將串連在一起形成一個新的調用列表,新的調用列表中包含了多個“進入點”。
委託的組合是使用二元運算子 + 和 += 來進行的,同樣可以使用 - 和 -= 來進行組合的移除。
下面的樣本示範多個委託的執行個體化及其相應的調用列表:
delegate void D(int x)class{ public static void M1(int i){...} public static void M2(int i){...}}class Test{ static void Main() { D cd1 = new D(c.M1); //M1 D cd2 = new D(c.M2); //M2 D cd3 = cd1 + cd2; //M1 + M2 D cd4 = cd3 + cd1; //M1 + M2 + M1 D cd5 = cd4 + cd3; //M1 + M2 + M1 + M2 }}
執行個體化cd1和cd2的時候,它們分別封裝一個方。執行個體化cd3的時候,它調用的列表有兩個方法M1和M2,而且順序與此相同。cd4的調用列表中依次包含M1、M2、M1。最後,cd5的調用列表中依次包含M2、M1、M1、M2。
4.委託的調用
當調用一個委託執行個體的時候,將按照調用列表的順序依次調用列表中的各個方法,當在調用期間發生異常,調用列表中排在後面的任何方法將不會被調用。
using System;delegate void D(int x);class C{ public static void M1(int i) { Console.WriteLine("C.M1:"+i); } public static void M2(int i) { Console.WriteLine("C.M1:"+i); } public void M3(int i) { Console.WriteLine("C.M2:"+i); }}class Test{ static void Main() { D cd1 = new D(c.M1); //M1 cd1(-1); //調用cd1 D cd2 = new D(c.M2); //M2 cd2(-2); //調用M2 D cd3 = cd1 + cd2; //M1 + M2 cd3(10); //依次調用M1、M2 cd3 += cd1; cd3(20); //依次調用M1、M2、M1 C c = new C(); D cd4 = new D(c.M3); cd3 += cd4; cd3(30); //依次調用M1、M2、M1、M3 cd3 -= cd1 //移除最後一個M1 cd3(40); //依次調用M1、M2、M3 cd3 -= cd4; cd3(50); //依次調用M1、M2 cd3 -= cd2 cd3(60); //調用M1 cd3 -= cd2 //這是cd3的調用列表中沒有cd2了,該移除不生效,不報錯 cd3(70); //調用M1 cd3 -= cd1 // cd3(70); //如果調用,將會報System.NullReferenceException異常 }}
Lambda運算式
自從C#3.0開始,就可以使用一種新文法把實現代碼賦予委託:Lambda運算式。只要有委託參數類型的地方,就可以使用Lambda運算式。使用匿名方法的地方可以使用Lambda運算式來代替,例如:
Func< string,string > = delagate(string para){ para += "Hello World!"; return param;} 可以寫成Func< string,string > = para=>{ para +="Hello World!"; return para;}
Lambda運算式運算子”=>”的左邊列出了需要的參數,右邊定義了賦予Lambda變數的方法的代碼實現。
我們注意到,無論何時,只要我們需要引入匿名方法,我們都需要在前面加上delegate關鍵字,而且參數列表都需要類型化。Lambda運算式提供了一種更為簡潔和自然的文法,而且在C#更多進階的方面中都涉及廣泛。簡而言之,我們應該用Lambda運算式來替代匿名方法。
例如:
public class Person{ string Name; int Age;}List<Person> personList = new List<Person>();personList.Find(delegate(Person p){retrun p.Age==12;});//可以寫成personList.Find(p=>p.Age==12);
[深入學習C#]匿名函數、委託和Lambda運算式