C# 委托 (一)—— 委托、 泛型委托與Lambda表達(dá)式
原創(chuàng)wnvalentin 最后發(fā)布于2018-08-19 20:46:47 閱讀數(shù) 7421? 收藏
展開
目錄
1 委托的含義
2 委托聲明、實(shí)例化和調(diào)用
2.1 委托的聲明
2.2 委托的實(shí)例化
2.3 委托實(shí)例的調(diào)用
3 泛型委托
3.1 Func委托
3.2 Action委托
3.3 Predicate委托
4 匿名委托
5 Lambda表達(dá)式
5.1 表達(dá)式Lambda
5.2 語(yǔ)句Lambda
1 委托的含義
當(dāng)需要將一個(gè)方法當(dāng)作另一個(gè)方法的參數(shù)時(shí),對(duì)于某些語(yǔ)言例如C/C++等,需要用函數(shù)指針來(lái)處理。而對(duì)于C#來(lái)說(shuō),則使用委托機(jī)制。
例如,當(dāng)我們需要對(duì)一個(gè)泛型集合ICollection<T>進(jìn)行排序時(shí),我們定義一個(gè)Sort方法,那么這個(gè)方法需要哪些參數(shù)才能進(jìn)行排序呢?首先,肯定需要一個(gè)Collection<T>對(duì)象作為輸入?yún)?shù),代表要排序的對(duì)象集合;然后,Sort方法還需要知道如何比較兩個(gè)對(duì)象,經(jīng)過(guò)比較之后才能決定讓哪個(gè)對(duì)象排在前面。因此,Sort方法需要第二個(gè)參數(shù),這個(gè)參數(shù)是一個(gè)方法,代表著比較排序?qū)ο蟮姆椒ā_@時(shí)候這個(gè)比較方法參數(shù)就只能是一個(gè)委托。我們?cè)谡{(diào)用Sort方法對(duì)具體的類進(jìn)行排序時(shí),通過(guò)委托傳入一個(gè)具體類的比較方法。
再比如,在使用LINQ時(shí),我們經(jīng)常會(huì)用到Where()、Find()等擴(kuò)展方法,實(shí)現(xiàn)對(duì)集合中元素的篩選或查找。Where()方法擁有一個(gè)Func<T>參數(shù),它就是一個(gè)委托。我們調(diào)用Where()時(shí)需要給這個(gè)委托傳遞一個(gè)方法,告訴Where篩選器篩選元素的規(guī)則方法。
委托是一種特殊的類,是一種能夠引用方法的類。在創(chuàng)建委托時(shí),就是創(chuàng)建了一個(gè)存儲(chǔ)方法引用的對(duì)象。
委托是類型安全的。C函數(shù)指針只是一個(gè)指向一個(gè)存儲(chǔ)單元的指針,不能保證指向的內(nèi)容就是正確類型的函數(shù)。而對(duì)于C#的委托而言,聲明一個(gè)委托時(shí)必須指定返回類型和參數(shù),.NET編譯器會(huì)嚴(yán)格檢查方法的參數(shù)和返回類型和委托是否匹配,檢查通過(guò)后才能進(jìn)行轉(zhuǎn)換。轉(zhuǎn)換之后的委托實(shí)例作為一個(gè)參數(shù),傳遞給調(diào)用它的函數(shù)。
一個(gè)委托可以被傳遞任何符合要求的方法。不同場(chǎng)合需要不同方法時(shí),在調(diào)用的地方直接將委托參數(shù)替換為實(shí)際方法就行。因此,委托調(diào)用的方法是在程序運(yùn)行時(shí)才能確定的。
2 委托聲明、實(shí)例化和調(diào)用
2.1 委托的聲明
前面提到過(guò),委托是一種特殊的類,因此委托的聲明與類的聲明方法類似,在任何可以聲明類的地方都可以聲明委托。委托聲明用delegate關(guān)鍵字,同時(shí)委托要指明方法參數(shù)和返回值,寫法與方法類似。綜合類的聲明和方法的聲明,委托聲明寫成如下形式:
[訪問(wèn)修飾符] delegate 返回值類型 委托名 (形參列表);
委托的聲明實(shí)際上是定義了一個(gè)派生于System.Delegate類的類,這與一般類的聲明語(yǔ)法不同。編譯器會(huì)根據(jù)委托的聲明自動(dòng)創(chuàng)建一個(gè)委托的類并實(shí)現(xiàn)細(xì)節(jié)。
接下來(lái)我們以一個(gè)簡(jiǎn)單的List<Student>排序?yàn)槔M(jìn)行說(shuō)明。假設(shè)我們有一個(gè)Student類,存放學(xué)生信息,擁有姓名、年齡和學(xué)號(hào)三個(gè)屬性:
public class Student
{
? ? public string Name { get; set; }
? ? public int Age { get; set; }
? ? public int Num { get; set; }? ?
}
然后創(chuàng)建一個(gè)List<Student>:
Student s1 = new Student() { Name = "小紅", Age = 10, Num = 1001 };
Student s2 = new Student() { Name = "小華", Age = 9, Num = 1002 };
List<Student> sList = new List<Student>();
sList.Add(s1);
List.Add(s2);
我們的目標(biāo)是想給List<Student>對(duì)象添加一個(gè)排序方法,這個(gè)排序方法可以根據(jù)年齡或者學(xué)號(hào)來(lái)排序,具體需要哪一種排序需要在客戶端調(diào)用時(shí)指定。(簡(jiǎn)單起見,本案例中List<Student>只包含兩個(gè)元素,不糾結(jié)于排序算法)
按照要求,Student對(duì)象的比較有兩種方法,我們實(shí)現(xiàn)兩個(gè)比較方法,供委托使用:
//比較年齡
public static bool Younger(Student s1, Student s2) => s1.Age <= s2.Age;
//比較學(xué)號(hào)
public static bool NumSmaller(Student s1, Student s2) => s1.Num <= s2.Num;
由上,我們可以抽象出一個(gè)代表比較Student的方法的委托:
public delegate bool CompareDelegate(Student first, Student second);
這個(gè)委托的類名為CompareDelegate,注意到委托聲明的返回值類型、參數(shù)與其代表的方法要完全一致。
2.2 委托的實(shí)例化
與普通類的使用方法相同,聲明了委托之后,我們必須給委托傳遞一個(gè)具體的方法,才能在運(yùn)行時(shí)調(diào)用委托實(shí)例。委托實(shí)例包含了被傳遞給它的方法的信息,在運(yùn)行時(shí),調(diào)用委托實(shí)例就相當(dāng)于執(zhí)行它當(dāng)中的方法。
委托實(shí)例化格式如下:
委托類名 委托實(shí)例名 = new 委托類名(Target)?;
其中,委托實(shí)例名是自定義的名稱,Target是要傳入的方法的名稱。注意,Target是方法的引用,不能帶()。帶()的話是該方法的調(diào)用。區(qū)分引用和調(diào)用。
委托的實(shí)例化還有一種簡(jiǎn)單的方法:
委托類名 委托實(shí)例名 = Target;
在需要委托實(shí)例的地方直接傳入Target引用即可,C#編譯器會(huì)自動(dòng)根據(jù)委托類型進(jìn)行驗(yàn)證,這稱為“委托推斷”。
案例:
//以下兩種方法等價(jià)
CompareDelegate myCompareDelegate = new CompareDelegate(Younger);
CompareDelegate myCompareDelegate = Younger;//委托推斷
2.3 委托實(shí)例的調(diào)用
委托實(shí)例等價(jià)于它當(dāng)中實(shí)際方法,因此可以使用反射的Invoke()方法調(diào)用委托實(shí)例,也可以直接在委托實(shí)例后加上()進(jìn)行調(diào)用。
我們下面看一下委托所代表的方法是如何被業(yè)務(wù)方法調(diào)用的。這里我們的業(yè)務(wù)是排序SortStudent方法:
//使用委托的業(yè)務(wù)方法
public static void SortStudent(List<Student> sList, CompareDelegate CompareMethod)
{
? ? if (CompareMethod(sList[0], sList[1]))//等價(jià)于CompareMethod.Invoke(sList[0],? List[1])
? ? {
? ? ? ? //sList[0]已經(jīng)在sList[1]前面了,所以什么也不用做
? ? }
? ? else
? ? {
? ? ? ? sList.Reverse();//交換位置
? ? }
? ? //獲取排名采用的比較方法的名稱
? ? Console.WriteLine($"\r\n按照 {CompareMethod.Method.Name} 排名:");
? ? //打印排序后的鏈表
? ? foreach (Student s in sList)
? ? ? ? Console.WriteLine($"{s.Name} {s.Age} {s.Num} ");
}
這里Sort方法擁有一個(gè)CompareDelegate類型的委托實(shí)例CompareMethod,它可直接當(dāng)做具體方法進(jìn)行調(diào)用。
在客戶端對(duì)委托進(jìn)行實(shí)例化后,調(diào)用SortStudent()方法就可以進(jìn)行排序了。
//委托的實(shí)例化與使用
CompareDelegate myCompareDelegate = NumSmaller;//采用比較學(xué)號(hào)的方法
SortStudent(sList, myCompareDelegate);
//使用委托推斷,與上兩行等價(jià)
SortStudent(sList, NumSmaller);
輸出如下:
按照 NumSmaller 排名:
小紅 10 1001
小華 9 1002
3 泛型委托
我們每次要使用一個(gè)委托時(shí),都需要先聲明這個(gè)委托類,規(guī)定參數(shù)和返回值類型,然后才能實(shí)例化、調(diào)用。為了簡(jiǎn)化這個(gè)過(guò)程,?.NET 框架為我們封裝了三個(gè)泛型委托類,因此大部分情況下我們不必再聲明委托,可以拿來(lái)直接實(shí)例化使用,方便了我們的日常Coding。
.這三種泛型委托包括:Func<T>委托、Action<T>委托和Predicate<T>委托。
3.1 Func<T>委托
Func<T>委托代表著擁有返回值的泛型委托。Func<T>有一系列的重載,形式如 Func<T1,T2, ... TResult>,其中TResult代表委托的返回值類型,其余均是參數(shù)類型。只有一個(gè)T時(shí),即Func<TResult>,代表該委托是無(wú)參數(shù)的。.NET封裝了最多16個(gè)輸入?yún)?shù)的Funct<>委托。
需要特別注意的是,若方法返回 void ,由于 void 不是數(shù)據(jù)類型,因此不能定義Func<void>委托。返回 void 的泛型委托見下文的Action<T>。
Func<T>的使用方法與一般的委托相同。例如上面的案例可改寫如下;
public static void SortStudent(List<Student> sList,Func<Student,Student,bool> CompareMethod)
{
? ? if(CompareMethod(sList[0], sList[1]))
? ? {
? ? }
? ? else
? ? {
? ? ? ? sList.Reverse();
? ? }
? ? Console.WriteLine($"\r\n按照 {CompareMethod.Method.Name} 排名:");
? ? foreach (Student s in sList)
? ? ? ? Console.WriteLine($"{s.Name} {s.Age} {s.Num} ");
}
//客戶端調(diào)用
Func<Student, Student, bool> myCompareFunc = NumSmaller;
SortStudent2(sList, myCompareFunc);
注意SortStudent2方法的委托參數(shù)也必須是Func<Student, Student, bool>,才能滿足方法調(diào)用時(shí)類型一致的要求。?
3.2 Action<T>委托
Action<T>委托代表返回值為空 void 的委托,它也有一些列重載,最多擁有16個(gè)輸入?yún)?shù)。用法與Func<T>相同。
3.3 Predicate<T>委托
這個(gè)一般用的較少,它封裝返回值為bool類型的委托,可被Func<T>代替。
4 匿名委托
采用匿名方法實(shí)例化的委托稱為匿名委托。
每次實(shí)例化一個(gè)委托時(shí),都需要事先定義一個(gè)委托所要調(diào)用的方法。為了簡(jiǎn)化這個(gè)流程,C# 2.0開始提供匿名方法來(lái)實(shí)例化委托。這樣,我們?cè)趯?shí)例化委托時(shí)就可以 “隨用隨寫” 它的實(shí)例方法。
使用的格式是:
委托類名 委托實(shí)例名 = delegate (args) {方法體代碼} ;
這樣就可以直接把方法寫在實(shí)例化代碼中,不必在另一個(gè)地方定義方法。當(dāng)然,匿名委托不適合需要采用多個(gè)方法的委托的定義。
使用匿名方法,以上代碼可改寫為:
CompareDelegate anonymousCompare = delegate (Student s3, Student s4)
{
? ? return s1.Num <= s2.Num;
};
SortStudent(sList, anonymousCompare);
需要說(shuō)明的是,匿名方法并不是真的“沒(méi)有名字”的,而是編譯器為我們自動(dòng)取一個(gè)名字。SortStudent方法打印了委托調(diào)用的方法的名字(見上文代碼),我們可以看到如下輸出:
按照 <Main>b__0 排名:
小紅 10 1001
小華 9 1002
?編譯器為我們的匿名方法取了一個(gè)b__0的名字。
5 Lambda表達(dá)式
江山代有才人出,縱然匿名方法使用很方便,可惜她很快就成了過(guò)氣網(wǎng)紅,沒(méi)能領(lǐng)多長(zhǎng)時(shí)間的風(fēng)騷。如今已經(jīng)很少見到了,因?yàn)閐elegate關(guān)鍵字限制了她用途的擴(kuò)展。自從C# 3.0開始,她就被Lambda表達(dá)式取代,而且Lambda表達(dá)式用起來(lái)更簡(jiǎn)單。Lambda表達(dá)式本質(zhì)上是改進(jìn)的匿名方法。
Lambda表達(dá)式的靈感可能是來(lái)源于數(shù)學(xué)中的函數(shù)表達(dá)式,例如下圖:

Lambda表達(dá)式把其中的箭頭用 => 符號(hào)表示。
如今Lambda表達(dá)式已經(jīng)應(yīng)用在很多地方了,例如方法體表達(dá)式(Expression-Bodied Methods)、自動(dòng)只讀屬性表達(dá)式等等。
Lambda表達(dá)式形式上分為兩種:
5.1 表達(dá)式Lambda
當(dāng)匿名函數(shù)只有一行代碼時(shí),可采用這種形式。例如:
CompareDelegate LambdaCompare = (s4, s5) => s4.Age <= s5.Age;
其中=>符號(hào)代表Lambda表達(dá)式,它的左側(cè)是參數(shù),右側(cè)是要返回或執(zhí)行的語(yǔ)句。參數(shù)要放在圓括號(hào)中,若只有一個(gè)參數(shù),為了方便起見可省略圓括號(hào)。有多個(gè)參數(shù)或者沒(méi)有參數(shù)時(shí),不可省略圓括號(hào)。
相比匿名函數(shù),在表達(dá)式Lambda中,方法體的花括號(hào){}和return關(guān)鍵字被省略掉了。
其實(shí),上文定義NumSmaller()和Younger()方法時(shí),由于這兩個(gè)方法主體只有一行代碼,所以用的也是表達(dá)式Lambda,這是Lambda表達(dá)式的推廣,?是C# 6 編譯器提供的一個(gè)語(yǔ)法糖。
5.2 語(yǔ)句Lambda
當(dāng)匿名函數(shù)有多行代碼時(shí),只能采用語(yǔ)句Lambda。例如,上面的表達(dá)式Lambda可改寫為語(yǔ)句Lambda:
CompareDelegate LambdaCompare = (s4, s5) =>
{
? ? return s4.Age <= s5.Age;
};
語(yǔ)句Lambda不可以省略{}和return語(yǔ)句。
————————————————
版權(quán)聲明:本文為CSDN博主「wnvalentin」的原創(chuàng)文章,遵循 CC 4.0 BY-SA 版權(quán)協(xié)議,轉(zhuǎn)載請(qǐng)附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/wnvalentin/article/details/81840339