2020-02-19

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容