C#中委托的基礎(chǔ)入門與實(shí)現(xiàn)方法
前言
似乎委托對(duì)于C#而言是一種高級(jí)屬性,但是我依舊希望你就算第一次看我的文章,也能有很大的收獲。
所以本博客的語(yǔ)言描述盡量簡(jiǎn)單易懂,知識(shí)點(diǎn)也是面向初入門對(duì)于委托不了解的學(xué)習(xí)者的。當(dāng)然如果有幸有大佬發(fā)現(xiàn)文章的錯(cuò)誤點(diǎn),也歡迎留言指出!
關(guān)于委托
關(guān)于委托的介紹主要來(lái)源于C#文檔:委托概述(本文章優(yōu)勢(shì)在于去掉一些不必要的細(xì)節(jié),對(duì)于初學(xué)者而言簡(jiǎn)單高效)
委托的定義主要是下面幾個(gè)方面:
- 委托是一種引用類型:表示對(duì)具有特定參數(shù)列表和返回類型的方法的引用
- 在實(shí)例化委托時(shí),你可以將其實(shí)例與任何具有兼容簽名和返回類型的方法相關(guān)聯(lián)。 你可以通過(guò)委托實(shí)例調(diào)用方法
委托本質(zhì)上來(lái)講就是將方法作為參數(shù)傳遞給其他方法的一種實(shí)現(xiàn)方式,當(dāng)然開(kāi)發(fā)者也可以直接去調(diào)用方法。但是當(dāng)一個(gè)項(xiàng)目擴(kuò)展到足夠大時(shí),這種直接調(diào)用的方式就會(huì)很復(fù)雜,難以維護(hù)。而委托不會(huì),可以很方便的進(jìn)行后期的擴(kuò)展開(kāi)發(fā)。只需要將自己的方法傳入已經(jīng)寫(xiě)好的對(duì)應(yīng)的委托即可。而不需要再在大量的代碼中找到調(diào)用處寫(xiě)入自己的方法。
關(guān)于委托的一些特點(diǎn)是(暫時(shí)不了解沒(méi)有關(guān)系):
- 委托類似于 C++ 函數(shù)指針,但委托完全面向?qū)ο?,不?C++ 指針會(huì)記住函數(shù),委托會(huì)同時(shí)封裝對(duì)象實(shí)例和方法。
- 委托允許將方法作為參數(shù)進(jìn)行傳遞。
- 委托可用于定義回調(diào)方法。
- 委托可以鏈接在一起;例如,可以對(duì)一個(gè)事件調(diào)用多個(gè)方法。
- 方法不必與委托類型完全匹配。 有關(guān)詳細(xì)信息,請(qǐng)參閱使用委托中的變體。
- 使用Lambda 表達(dá)式可以更簡(jiǎn)練地編寫(xiě)內(nèi)聯(lián)代碼塊。 Lambda表達(dá)式(在某些上下文中)可編譯為委托類型。 若要詳細(xì)了解lambda 表達(dá)式,請(qǐng)參閱 lambda 表達(dá)式。
如果對(duì)于一個(gè)初學(xué)者,你可以簡(jiǎn)單的理解,委托就是一個(gè)更高級(jí)的調(diào)用方法的方式,而你要學(xué)習(xí)的,就是這種方式的實(shí)現(xiàn)方法,然后后期再慢慢理解更多的細(xì)節(jié)。
委托的實(shí)現(xiàn)
一、基本實(shí)現(xiàn)方式
前面也說(shuō),委托是一個(gè)引用類型,要使用委托,肯定就需要對(duì)其進(jìn)行定義并創(chuàng)建一個(gè)委托對(duì)象,下面使用一個(gè)帶有一個(gè)參數(shù)的案例來(lái)理解委托
public class DemoDelegate { //T1: delegate void TestDel(string s); TestDel Del; //T4: static void Main(string[] args) { DemoDelegate demo = new DemoDelegate(); demo.CreateDelObject(); demo.Del?.Invoke("你們好"); } //T3: public void CreateDelObject() { Del += TestEventOne; Del += TestEventTwo; } //T2: public void TestEventOne(string str) { Console.WriteLine(str); } public void TestEventTwo(string str) { Console.WriteLine(str); } }
如果你是剛剛接觸委托這個(gè)概念,可能對(duì)于這些代碼的含義不是特別了解,沒(méi)關(guān)系,你可以根據(jù)注釋的順序來(lái)看代碼并理解委托的實(shí)現(xiàn)機(jī)制:
- T1:通過(guò)delegate關(guān)鍵字定義一個(gè)委托類型TestDel,并創(chuàng)建一個(gè)實(shí)例Del
- T2: 也很好理解,創(chuàng)建兩個(gè)測(cè)試方法,可以執(zhí)行輸出
- T3: 是委托的關(guān)鍵,為剛剛創(chuàng)建的委托來(lái)添加事件方法
- T4: 執(zhí)行委托實(shí)例Del,可以簡(jiǎn)單理解為執(zhí)行該實(shí)例綁定的所有事件方法
當(dāng)然有一些語(yǔ)法是比較獨(dú)特的,比如說(shuō)+=這樣的語(yǔ)法,就是一種為委托添加事件方法的方法,而對(duì)于執(zhí)行委托語(yǔ)句demo.Del?.Invoke("你們好");
中Invoke()為執(zhí)行該委托對(duì)象內(nèi)方法的API,?則可以在Del委托對(duì)象為空時(shí),系統(tǒng)不報(bào)錯(cuò)
關(guān)于?的具體含義,可查閱:
可為空引用類型
其實(shí)委托主要是有這簡(jiǎn)單的四步來(lái)實(shí)現(xiàn)了,通過(guò)這個(gè)案例,更加明顯的體現(xiàn)出委托將一系列方法作為參數(shù)來(lái)讓其他方法去調(diào)用的特點(diǎn)。
二、使用委托時(shí)的一些特殊方式
通過(guò)上面的案例,可以看出對(duì)于委托的實(shí)現(xiàn)是很簡(jiǎn)單的,但是C#還是為我們提供了很多更加間接或者集成的用法,具體有:
1、委托實(shí)例對(duì)象的創(chuàng)建多元化:
創(chuàng)建委托類型的多種方式:
- 直接使用New來(lái)創(chuàng)建一個(gè)對(duì)象,但是注意,在New時(shí)需要綁定直接添加一個(gè)事件方法,不然會(huì)報(bào)錯(cuò)(這也是與其他引用類型不同的地方)
- 使用直接賦值的方式創(chuàng)建
- 定義方法名,后期添加事件方法時(shí)自動(dòng)實(shí)例(上面的例子)
關(guān)于具體的實(shí)現(xiàn)代碼:
public class DemoDelegate { delegate void TestDel(string str); //第一種:New 的同時(shí)綁定方法 TestDel DelOne = new TestDel(TestEventOne); //第二種 TestDel DelTwo = TestEventOne; static void TestEventOne(string str) { Console.WriteLine(str); } }
注意,關(guān)于第三種后期綁定有一種現(xiàn)象值得留意,在使用后期綁定時(shí),如果你是創(chuàng)建一個(gè)成員變量(全局變量)委托類型,后期綁定可以直接使用+=來(lái)增加委托綁定的方法,而你如果是創(chuàng)建一個(gè)局部變量的委托,需要先通過(guò)=
來(lái)添加一個(gè)方法后,才能使用+=來(lái)增加方法,不然就會(huì)報(bào)空,如圖:
出現(xiàn)這種情況的原因在于成員變量與局部變量之間的區(qū)別,如果想要了解更多,可以執(zhí)行百度,這邊列出兩者在本案例中的區(qū)別:
成員變量:有默認(rèn)初始化值局部變量:沒(méi)有默認(rèn)初始化值,必須定義,賦值,然后才能使用。
2、事件綁定的多種方式
對(duì)于事件的綁定,可以使用的方式有很多,在不同的情況下不同的方式也有不同的優(yōu)勢(shì)與局限性,可以根據(jù)自己的需求進(jìn)行自行選擇
- 使用方法名通過(guò)+=來(lái)添加方法,可以通過(guò)-=來(lái)刪除方法
- 使用匿名方法
- 使用Lambda表達(dá)式
關(guān)于第一種方法,已經(jīng)在上面表示的很清楚。
關(guān)于社會(huì)的進(jìn)步與發(fā)展,某一方面來(lái)講,是由于人類的懶來(lái)驅(qū)動(dòng)的,C#開(kāi)發(fā)人員可能覺(jué)得第一種方式太復(fù)雜了,于是就出現(xiàn)匿名函數(shù)的腳本,先通過(guò)代碼來(lái)看一下其實(shí)現(xiàn)方式:
public class DemoDelegate { delegate void TestDel(string str); TestDel Del; static void Main(string[] args) { DemoDelegate demo = new DemoDelegate(); //匿名方法使用方式演示 demo.Del = delegate (string str) { Console.WriteLine("這是一個(gè)匿名方法的測(cè)試"); }; } }
通過(guò)上面的代碼可以看出,通過(guò)匿名方式使得我們不需要重新定義方法來(lái)進(jìn)行綁定,只需要通過(guò)委托關(guān)鍵字,而省去數(shù)據(jù)類型修飾符、方法簽名等結(jié)構(gòu)
匿名方法定義(菜鳥(niǎo)教程):
匿名方法提供了一種傳遞代碼塊作為委托參數(shù)的技術(shù)。匿名方法是沒(méi)有名稱只有主體的方法。在匿名方法中不需要指定返回類型,它是從方法主體內(nèi)的 return 語(yǔ)句推斷的()
而Lambda 表達(dá)式更加極致,將能省掉的東西全部省掉,使得最終的表達(dá)式極其的簡(jiǎn)潔:
public class DemoDelegate { delegate void TestDel(string str); TestDel Del; static void Main(string[] args) { DemoDelegate demo = new DemoDelegate(); //lambda表達(dá)式,括號(hào)內(nèi)為參數(shù)變量名,如果沒(méi)有直接() demo.Del += (str)=> { Console.WriteLine(str); }; } }
可以通過(guò)腳本看出,Lambda 表達(dá)式對(duì)于語(yǔ)法的節(jié)省到達(dá)了極致,去掉了所有的修飾符,包括傳入的參數(shù)的數(shù)據(jù)類型修飾符,只需要一個(gè)變量名即可,而一個(gè)委托如果沒(méi)有參數(shù),直接使用()即可,簡(jiǎn)單到極致
注意(關(guān)于()的應(yīng)用):
如果Lambda 無(wú)參數(shù),則必須要有()
有一個(gè)參數(shù)可以去掉(),直接+= str=>{};
如果有多個(gè)參數(shù),也必須要有()
三、委托的幾種特殊實(shí)現(xiàn)方式
除了使用delegate關(guān)鍵字來(lái)實(shí)現(xiàn)委托外,C#還提供了幾種升級(jí)版的集成化的使用方式,比如Action和Func方法等等。但是注意,高的集成化往往意味著低的適配性。所以對(duì)于下面介紹幾種方式他們往往有一定的使用限制,不如delegate來(lái)的靈活,不過(guò)對(duì)于特定場(chǎng)景更加的簡(jiǎn)單快捷
1,使用Action方法
在開(kāi)始介紹使用方式之前,先說(shuō)明一下其使用特殊
- 對(duì)于沒(méi)有返回值的委托,簡(jiǎn)單的理解就是不需要return(這種想法是錯(cuò)誤的,但是好理解)
其實(shí)很簡(jiǎn)單的對(duì)不對(duì),其腳本實(shí)現(xiàn)更加簡(jiǎn)單:
//無(wú)參數(shù)的Action委托對(duì)象的定義 Action<> actDemoOne; //帶參數(shù)的Action委托對(duì)象的定義,參數(shù)最多十六個(gè) Action<int> actDemoTwo;
除了定義不同外,創(chuàng)建的委托實(shí)例對(duì)于方法的綁定與執(zhí)行與標(biāo)準(zhǔn)的委托相同,其實(shí)Action本來(lái)就是通過(guò)delegate來(lái)定義的一個(gè)委托類型,但是這個(gè)定義是C#系統(tǒng)進(jìn)行定義的。
在代碼中我們很容易找到這句定義
而帶參數(shù)的類型與上面類似
這樣我們可以直接使用C#提供定義好的委托類型來(lái)創(chuàng)建我們的委托實(shí)例,這種方式的優(yōu)勢(shì)就是代碼結(jié)構(gòu)簡(jiǎn)潔好用。不過(guò)限制就是只能綁定沒(méi)有返回值的方法
2,使用Func方法
其實(shí)Func的用法是與Action相反互補(bǔ)的,其主要的特點(diǎn)是有返回值,為了突出,依舊列出來(lái)
- 主要用于沒(méi)有參數(shù)的委托類型,且必須有返回值
- 但是同時(shí)是可以有參數(shù)的,并不是與Action完全相反
通過(guò)代碼來(lái)表述這一特點(diǎn):
// int為該委托綁定方法的返回值類型,注意必須要有返回值 Func<int> funDemo; //綁定與執(zhí)行與標(biāo)準(zhǔn)委托相同,來(lái)復(fù)習(xí)一下 public class DemoDelegate { static void Main(string[] args) { DemoDelegate demo = new DemoDelegate(); Func<int> funDemo; //為委托添加方法 funDemo = demo.TestEventOne; //執(zhí)行委托 funDemo?.Invoke(); //依舊可以使用Lambda表達(dá)式 funDemo += () => { return 0; }; /*-----------帶參數(shù)的Func用法--------*/ Func<string,int> funDemoTwo; funDemoTwo = demo.TestEventTwo; } public int TestEventOne() { return 0; } public int TestEventTwo(string str) { Console.WriteLine(str); return 0; } }
上面的代碼將兩種情況放在一起解釋的,可以分開(kāi)邏輯進(jìn)行理解,但是重要的是要理解帶參數(shù)的Func的用法,我們可以看到Func<string,int>
有兩個(gè)數(shù)據(jù)類型的寫(xiě)入,前面一個(gè)就是傳入?yún)?shù)的類型,而后面就是返回值類型,一定要區(qū)分開(kāi)來(lái)
四、委托的一些特殊小知識(shí)
1、委托閉包的產(chǎn)生
在正式開(kāi)始介紹閉包概念之前,先通過(guò)一個(gè)案例來(lái)發(fā)掘關(guān)于閉包產(chǎn)生的現(xiàn)象,你可以猜想一下下面的代碼的輸出結(jié)果:
class DemoDelegate { delegate void TestDel(); TestDel Del; static void Main(string[] args) { DemoDelegate demo = new DemoDelegate(); for (int i = 0; i < 3; i++) { demo.Del+= () => { Console.WriteLine(i); }; } demo.Del?.Invoke(); } }
如果根據(jù)正常的邏輯思路去判斷,會(huì)很直接的判斷得到的輸出為:0、1、2,但是經(jīng)過(guò)執(zhí)行打印卻發(fā)現(xiàn)最終的輸出結(jié)果為:3、3、3(為啥不是2、2、2,思考一下++i與i++,或者往后看)
這樣的結(jié)果確實(shí)很奇妙,但是如果認(rèn)真思考,你可能會(huì)有一些小想法,是不是由于存儲(chǔ)的是數(shù)據(jù)i地址呢,而導(dǎo)致最終結(jié)果相同呢。其實(shí)可以簡(jiǎn)單的理解成這個(gè)樣子。
如果想要更加深入的了解閉包,希望下面的一些解釋可以幫助你理解
閉包概念:
是一個(gè)函數(shù)與其他相關(guān)引用環(huán)境組合的實(shí)體,而在引用環(huán)境消失后,該函數(shù)會(huì)依舊保存從函數(shù)內(nèi)引用的變量
根據(jù)上面的例子來(lái)翻譯一下就是委托的代碼塊使用了代碼塊外的變量,而這個(gè)變量是Test()方法的局部變量,當(dāng)Test()方法執(zhí)行完畢后,這個(gè)局部變量本應(yīng)該被銷毀,但是由于閉包原因卻保存其狀態(tài)到內(nèi)存中,來(lái)保證自己后續(xù)的使用。
因此,所有的委托方法需要的變量?jī)?nèi)存地址都指向了本應(yīng)被銷毀的局部變量i,最終的讀取就是i最后的狀態(tài),也就是全是經(jīng)過(guò)三次i++后的3
閉包需要注意的問(wèn)題
如果使用必要,需要注意由于閉包而產(chǎn)生的內(nèi)存泄露的問(wèn)題,由于閉包是訪問(wèn)另一個(gè)函數(shù)中的變量,就會(huì)影響另一函數(shù)中的局部變量的內(nèi)存回收。而一直占用在內(nèi)存中。造成內(nèi)存泄露的后果。
但是也有另外的觀點(diǎn)講閉包不會(huì)造成內(nèi)存泄露。原因在于C#的垃圾回收是有相應(yīng)的處理的。由于本人對(duì)于垃圾回收機(jī)制認(rèn)識(shí)比較淺顯,目前也做不出判斷。還希望了解的人可以留言告知
2,關(guān)于事件
在C#中,也提出了事件這個(gè)概念,本質(zhì)上來(lái)講也是委托,但是是一個(gè)受限的委托
與靈活的委托不同,事件只能在定義的類內(nèi)被調(diào)用,不過(guò)可以在其他類里面進(jìn)行方法的綁定
事件的用法
在我們定義一個(gè)委托類型后,我們可以通過(guò)event創(chuàng)建一個(gè)事件實(shí)例:
通過(guò)上面的案例可以看到,使用event修飾委托實(shí)例后,只能夠在定義類中執(zhí)行委托的方法,而不能在其他類中去調(diào)用執(zhí)行
總結(jié)
關(guān)于委托的內(nèi)容還是挺多的,但是大多都是對(duì)于本質(zhì)的東西進(jìn)行的一些擴(kuò)展,在學(xué)習(xí)初期,只需要掌握關(guān)于delegate的核心的用法即可,后續(xù)再慢慢的延申擴(kuò)展
到此這篇關(guān)于C#中委托的基礎(chǔ)入門與實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)C#委托基礎(chǔ)入門內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Unity實(shí)現(xiàn)輪盤方式的按鈕滾動(dòng)效果
這篇文章主要為大家詳細(xì)介紹了Unity實(shí)現(xiàn)輪盤方式的按鈕滾動(dòng)效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-02-02Unity實(shí)現(xiàn)角色受擊身體邊緣發(fā)光特效
這篇文章主要為大家詳細(xì)介紹了Unity實(shí)現(xiàn)角色受擊身體邊緣發(fā)光特效,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-04-04C#實(shí)現(xiàn)把科學(xué)計(jì)數(shù)法(E)轉(zhuǎn)化為正常數(shù)字值
這篇文章主要介紹了C#實(shí)現(xiàn)把科學(xué)計(jì)數(shù)法(E)轉(zhuǎn)化為正常數(shù)字值,本文直接給出代碼實(shí)例,需要的朋友可以參考下2015-06-06C# Hashtable/Dictionary寫(xiě)入和讀取對(duì)比詳解
本文中將從基礎(chǔ)角度講解HashTable、Dictionary的構(gòu)造和通過(guò)程序進(jìn)行插入讀取對(duì)比2013-11-11C# 實(shí)現(xiàn)Table的Merge,Copy和Clone
這篇文章主要介紹了C# 實(shí)現(xiàn)Table的Merge,Copy和Clone,幫助大家更好的利用c#處理文件,感興趣的朋友可以了解下2020-12-12C#圖像處理之圖像目標(biāo)質(zhì)心檢測(cè)的方法
這篇文章主要介紹了C#圖像處理之圖像目標(biāo)質(zhì)心檢測(cè)的方法,可實(shí)現(xiàn)C#計(jì)算圖像質(zhì)心的相關(guān)技巧,需要的朋友可以參考下2015-04-04C#實(shí)現(xiàn)任意數(shù)據(jù)類型轉(zhuǎn)成json格式輸出
C#實(shí)現(xiàn)任意數(shù)據(jù)類型轉(zhuǎn)成json格式輸出。需要的朋友可以過(guò)來(lái)參考下,希望對(duì)大家有所幫助2013-10-10WPF實(shí)現(xiàn)動(dòng)畫(huà)效果(二)之From/To/By動(dòng)畫(huà)
這篇文章介紹了WPF實(shí)現(xiàn)動(dòng)畫(huà)效果之From/To/By動(dòng)畫(huà),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-06-06