深入理解C#中的Delegate
在c#中,event與delegate是兩個(gè)非常重要的概念。因?yàn)樵赪indows應(yīng)用程序中,對(duì)事件的使用非常頻繁,而事件的實(shí)現(xiàn)依賴于delegate。
下面是對(duì)網(wǎng)上一些比較好的關(guān)于delegage的資料的整理,以及自己的一些想法。
Delegate是什么?
Delegate中文翻譯為“委托”。Msdn中對(duì)Delegate的解釋如下:
C#中的委托類似于C或C++中的函數(shù)指針。使用委托使程序員可以將方法引用封裝在委托對(duì)象內(nèi)。然后可以將該委托對(duì)象傳遞給可調(diào)用所引用方法的代碼,而不必在編譯時(shí)知道將調(diào)用哪個(gè)方法。與C或C++中的函數(shù)指針不同,委托是面向?qū)ο?、類型安全的,并且是安全的?br />
一旦為委托分配了方法,委托將與該方法具有完全相同的行為。委托方法的使用可以像其他任何方法一樣,具有參數(shù)和返回值,如下面的示例所示:
public delegate void Del(string message);
與委托的簽名(由返回類型和參數(shù)組成)匹配的任何方法都可以分配給該委托。這樣就可以通過編程方式來更改方法調(diào)用,還可以向現(xiàn)有類中插入新代碼。只要知道委托的簽名,便可以分配自己的委托方法。
調(diào)用委托
構(gòu)造委托對(duì)象時(shí),通常提供委托將包裝的方法的名稱或使用匿名方法。實(shí)例化委托后,委托將把對(duì)它進(jìn)行的方法調(diào)用傳遞給方法。調(diào)用方傳遞給委托的參數(shù)被傳遞給方法,來自方法的返回值(如果有)由委托返回給調(diào)用方。這被稱為調(diào)用委托??梢詫⒁粋€(gè)實(shí)例化的委托視為被包裝的方法本身來調(diào)用該委托。例如:
// Create a method for a delegate. public static void DelegateMethod(string message) { System.Console.WriteLine(message); } // Instantiate the delegate. Del handler = DelegateMethod; // Call the delegate. handler("Hello World");
將委托構(gòu)造為包裝實(shí)例方法時(shí),該委托將同時(shí)引用實(shí)例和方法。除了它所包裝的方法外,委托不了解實(shí)例類型,所以只要任意類型的對(duì)象中具有與委托簽名相匹配的方法,委托就可以引用該對(duì)象。將委托構(gòu)造為包裝靜態(tài)方法時(shí),它只引用方法。
回調(diào)
由于實(shí)例化委托是一個(gè)對(duì)象,所以可以將其作為參數(shù)進(jìn)行傳遞,也可以將其賦值給屬性。這樣,方法便可以將一個(gè)委托作為參數(shù)來接受,并且以后可以調(diào)用該委托。這稱為異步回調(diào),是在較長的進(jìn)程完成后用來通知調(diào)用方的常用方法。以這種方式使用委托時(shí),使用委托的代碼無需了解有關(guān)所用方法的實(shí)現(xiàn)方面的任何信息。此功能類似于接口所提供的封裝。
回調(diào)的另一個(gè)常見用法是定義自定義的比較方法并將該委托傳遞給排序方法。它允許調(diào)用方的代碼成為排序算法的一部分。下面的示例方法使用 Del 類型作為參數(shù):
public void MethodWithCallback(int param1, int param2, Del callback) { callback("The number is: " + (param1 + param2).ToString()); }
然后可以將上面創(chuàng)建的委托傳遞給該方法:
MethodWithCallback(1, 2, handler);
在控制臺(tái)中將收到下面的輸出:
The number is: 3
使用委托的好處
委托允許類設(shè)計(jì)器分離類型聲明和實(shí)現(xiàn)。
在將委托用作抽象概念時(shí),MethodWithCallback 不需要直接調(diào)用控制臺(tái) -- 設(shè)計(jì)它時(shí)無需考慮控制臺(tái)。MethodWithCallback 的作用只是準(zhǔn)備字符串并將該字符串傳遞給其他方法。此功能特別強(qiáng)大,因?yàn)槲械姆椒梢允褂萌我鈹?shù)量的參數(shù)。
將方法作為參數(shù)進(jìn)行引用的能力使委托成為定義回調(diào)方法的理想選擇。例如,可以向排序算法傳遞對(duì)比較兩個(gè)對(duì)象的方法的引用。分離比較代碼使得可以采用更通用的方式編寫算法。
如何使用委托
1. 聲明委托
聲明一個(gè)新的委托類型。每個(gè)委托類型都描述參數(shù)的數(shù)目和類型,以及它可以封裝的方法的返回值類型。每當(dāng)需要一組新的參數(shù)類型或新的返回值類型時(shí),都必須聲明一個(gè)新的委托類型。
2. 實(shí)例化委托
聲明了委托類型后,必須創(chuàng)建委托對(duì)象并使之與特定方法關(guān)聯(lián)。方法的簽名應(yīng)與委托定義的簽名一致。
委托對(duì)象可以關(guān)聯(lián)靜態(tài)方法,也可以關(guān)聯(lián)非靜態(tài)方法。
委托一旦創(chuàng)建,它的關(guān)聯(lián)方法就不能更改;委托對(duì)象是不可變的。
3. 調(diào)用委托
創(chuàng)建委托對(duì)象后,通常將委托對(duì)象傳遞給將調(diào)用該委托的其他代碼。通過委托對(duì)象的名稱(后面跟著要傳遞給委托的參數(shù),括在括號(hào)內(nèi))調(diào)用委托對(duì)象。
下面是一個(gè)示例:
using System; public class SamplesDelegate { // Declares a delegate for a method that takes in an int and returns a String. public delegate String myMethodDelegate(int myInt); // Defines some methods to which the delegate can point. public class mySampleClass { // Defines an instance method. public String myStringMethod(int myInt) { if (myInt > 0) return ("positive"); if (myInt < 0) return ("negative"); return ("zero"); } // Defines a static method. public static String mySignMethod(int myInt) { if (myInt > 0) return ("+"); if (myInt < 0) return ("-"); return (""); } } public static void Main() { // Creates one delegate for each method. mySampleClass mySC = new mySampleClass(); myMethodDelegate myD1 = new myMethodDelegate(mySC.myStringMethod); myMethodDelegate myD2 = new myMethodDelegate(mySampleClass.mySignMethod); // Invokes the delegates. Console.WriteLine("{0} is {1}; use the sign /"{2}/".", 5, myD1(5), myD2(5)); Console.WriteLine("{0} is {1}; use the sign /"{2}/".", -3, myD1(-3), myD2(-3)); Console.WriteLine("{0} is {1}; use the sign /"{2}/".", 0, myD1(0), myD2(0)); } } /* This code produces the following output: 5 is positive; use the sign "+". -3 is negative; use the sign "-". 0 is zero; use the sign "". */
多路廣播
調(diào)用委托時(shí),它可以調(diào)用多個(gè)方法。這稱為多路廣播。若要向委托的方法列表(調(diào)用列表)中添加額外的方法,只需使用加法運(yùn)算符或加法賦值運(yùn)算符(“+”或“+=”)添加兩個(gè)委托。例如:
MethodClass obj = new MethodClass(); Del d1 = obj.Method1; Del d2 = obj.Method2; Del d3 = DelegateMethod; //Both types of assignment are valid. Del allMethodsDelegate = d1 + d2; allMethodsDelegate += d3;
此時(shí),allMethodsDelegate 在其調(diào)用列表中包含三個(gè)方法 -- Method1、Method2 和 DelegateMethod。原來的三個(gè)委托 d1、d2 和 d3 保持不變。調(diào)用 allMethodsDelegate 時(shí),將按順序調(diào)用所有這三個(gè)方法。如果委托使用引用參數(shù),則引用將依次傳遞給三個(gè)方法中的每個(gè)方法,由一個(gè)方法引起的更改對(duì)下一個(gè)方法是可見的。如果任一方法引發(fā)了異常,而在該方法內(nèi)未捕獲該異常,則該異常將傳遞給委托的調(diào)用方,并且不再對(duì)調(diào)用列表中后面的方法進(jìn)行調(diào)用。如果委托具有返回值和/或輸出參數(shù),它將返回最后調(diào)用的方法的返回值和參數(shù)。若要從調(diào)用列表中移除方法,請(qǐng)使用減法運(yùn)算符或減法賦值運(yùn)算符(“-”或“-=”)。例如:
//remove Method1 allMethodsDelegate -= d1; // copy AllMethodsDelegate while removing d2 Del oneMethodDelegate = allMethodsDelegate - d2;
多路廣播委托廣泛用于事件處理中。事件源對(duì)象向已注冊(cè)接收該事件的接收方對(duì)象發(fā)送事件通知。為了為事件注冊(cè),接收方創(chuàng)建了旨在處理事件的方法,然后為該方法創(chuàng)建委托并將該委托傳遞給事件源。事件發(fā)生時(shí),源將調(diào)用委托。然后,委托調(diào)用接收方的事件處理方法并傳送事件數(shù)據(jù)。給定事件的委托類型由事件源定義。
本示例演示如何組合多路廣播委托。委托對(duì)象的一個(gè)用途在于,可以使用 + 運(yùn)算符將它們分配給一個(gè)要成為多路廣播委托的委托實(shí)例。組合的委托可調(diào)用組成它的那兩個(gè)委托。只有相同類型的委托才可以組合。
運(yùn)算符可用來從組合的委托移除組件委托。
delegate void Del(string s); class TestClass { static void Hello(string s) { System.Console.WriteLine(" Hello, {0}!", s); } static void Goodbye(string s) { System.Console.WriteLine(" Goodbye, {0}!", s); } static void Main() { Del a, b, c, d; // Create the delegate object a that references // the method Hello: a = Hello; // Create the delegate object b that references // the method Goodbye: b = Goodbye; // The two delegates, a and b, are composed to form c: c = a + b; // Remove a from the composed delegate, leaving d, // which calls only the method Goodbye: d = c - a; System.Console.WriteLine("Invoking delegate a:"); a("A"); System.Console.WriteLine("Invoking delegate b:"); b("B"); System.Console.WriteLine("Invoking delegate c:"); c("C"); System.Console.WriteLine("Invoking delegate d:"); d("D"); } }
Delegate的總結(jié)
delegate是C#中的一種類型,它實(shí)際上是一個(gè)能夠持有對(duì)某個(gè)方法的引用的類。與其它的類不同,delegate類能夠擁有一個(gè)簽名(signature),并且它只能持有與它的簽名相匹配的方法的引用。它所實(shí)現(xiàn)的功能與C/C++中的函數(shù)指針十分相似。它允許傳遞一個(gè)類A的方法m給另一個(gè)類B的對(duì)象,使得類B的對(duì)象能夠調(diào)用這個(gè)方法m。但與函數(shù)指針相比,delegate有許多函數(shù)指針不具備的優(yōu)點(diǎn)。首先,函數(shù)指針只能指向靜態(tài)函數(shù),而delegate既可以引用靜態(tài)函數(shù),又可以引用非靜態(tài)成員函數(shù)。在引用非靜態(tài)成員函數(shù)時(shí),delegate不但保存了對(duì)此函數(shù)入口指針的引用,而且還保存了調(diào)用此函數(shù)的類實(shí)例的引用。其次,與函數(shù)指針相比,delegate是面向?qū)ο蟆㈩愋桶踩?、可靠的受控(managed)對(duì)象。也就是說,runtime能夠保證delegate指向一個(gè)有效的方法,你無須擔(dān)心delegate會(huì)指向無效地址或者越界地址。
自己對(duì)Delegate的理解
“delegate是C#中的一種類型。”delegate與class是類似的,class定義一種類型,delegate也定義一種類型。class可以定義各種各樣的類,如classA、classB,而delegate可以定義各種各樣的代理,如delegate1,delegate2。與class不同的是,delegate的定義沒有字段、屬性、方法等,只有簽名(返回值及參數(shù))。
“它實(shí)際上是一個(gè)能夠持有對(duì)某個(gè)方法的引用的類?!眃elegate對(duì)象可以持有對(duì)某個(gè)方法的引用,這個(gè)方法的簽名必須與代理類型的簽名一致(這就是“delegate定義回調(diào)方法的接口”這一說法的原由)。代理對(duì)象持有對(duì)這個(gè)方法的引用,當(dāng)調(diào)用代理對(duì)象時(shí),即實(shí)現(xiàn)對(duì)這個(gè)方法的調(diào)用。之所以能通過調(diào)用代理對(duì)象來實(shí)現(xiàn)對(duì)方法的調(diào)用,是因?yàn)樵趯?shí)例化代理對(duì)象時(shí),把傳入方法的地址賦給了代理對(duì)象,使得當(dāng)調(diào)用代理對(duì)象時(shí),內(nèi)存中的指令指針即指向傳入方法的入口,執(zhí)行傳入方法的方法體。
利用代理來實(shí)現(xiàn)多路廣播時(shí),即把多個(gè)方法的引用(即內(nèi)存地址)保存到代理的方法引用隊(duì)列。調(diào)用代理對(duì)象時(shí),根據(jù)代理對(duì)象的方法引用隊(duì)列,內(nèi)存中的指令指針即逐個(gè)指向每個(gè)方法的入口,按次序執(zhí)行每個(gè)方法的方法體。
事件的發(fā)布與訂閱。所謂事件,就是指當(dāng)某個(gè)特定的事情發(fā)生時(shí),類或?qū)ο笸ㄟ^事件通知關(guān)注此事情的類或?qū)ο蟆0l(fā)送(或引發(fā))事件的類稱為“發(fā)行者”,接收(或處理)事件的類稱為“訂戶”。事件的實(shí)現(xiàn)是依賴于代理的。事件的實(shí)現(xiàn)原理即,訂戶把響應(yīng)事件的方法傳遞給發(fā)行者,當(dāng)特定的事情發(fā)生時(shí),發(fā)行者能夠調(diào)用這些響應(yīng)事件的方法。在代碼級(jí)的實(shí)現(xiàn)即,發(fā)行者定義一個(gè)delegate類型,提供一個(gè)public的delegate對(duì)象作為字段或?qū)傩?;訂戶(可以是多個(gè))通過將響應(yīng)事件的方法傳遞給delegate類型來實(shí)例化一個(gè)delegate對(duì)象,并通過+=運(yùn)算符,將delegate對(duì)象賦值給發(fā)行者的delegate對(duì)象,實(shí)際上就是多路廣播。當(dāng)特定的事情發(fā)生時(shí),發(fā)行者調(diào)用delegate對(duì)象,即調(diào)用所有訂戶的響應(yīng)事件的方法。
以上所述是小編給大家介紹的深入理解C#中的Delegate ,希望對(duì)大家有所幫助,如果大家有任何疑問請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
相關(guān)文章
C#采用Winform實(shí)現(xiàn)類似Android的Listener
這篇文章主要介紹了C#采用Winform實(shí)現(xiàn)類似Android的Listener,很實(shí)用的技巧,需要的朋友可以參考下2014-08-08數(shù)字金額大寫轉(zhuǎn)換器制作代碼分享(人民幣大寫轉(zhuǎn)換)
一個(gè)人民幣大寫的擴(kuò)展方法,可以做成數(shù)字金額大寫轉(zhuǎn)換器,大家參考使用吧2013-12-12WPF開發(fā)之實(shí)現(xiàn)一種三軸機(jī)械手控件
這篇文章主要為大家詳細(xì)介紹了如何利用WPF實(shí)現(xiàn)簡(jiǎn)單一種三軸機(jī)械手控件,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)或工作有一定幫助,感興趣的小伙伴可以了解一下2023-01-01C#實(shí)現(xiàn)集合轉(zhuǎn)換成json格式數(shù)據(jù)的方法
這篇文章主要介紹了C#實(shí)現(xiàn)集合轉(zhuǎn)換成json格式數(shù)據(jù)的方法,涉及C#針對(duì)dataTable、Enumerable及Json格式數(shù)據(jù)的遍歷及轉(zhuǎn)換操作相關(guān)技巧,需要的朋友可以參考下2016-07-07C#計(jì)算輸入漢字GBK編碼后十六進(jìn)制數(shù)輸出的方法
這篇文章主要介紹了C#計(jì)算輸入漢字GBK編碼后十六進(jìn)制數(shù)輸出的方法,涉及C#編碼操作相關(guān)技巧,需要的朋友可以參考下2015-04-04