目前大部分文章關注是如何使用委托?為什么要使用委托?
卻很少關注委托是什么?委托是如何工作的?明白這兩個問題能幫助我們更好的理解使用委托。
本文的內容 就是針對這兩個問題。
先看一個最簡單的例子
1 class PRogram 2 { 3 delegate void TestDelegate(int val); 4 static void Main(string[] args) 5 { 6 TestDelegate Dele = new TestDelegate(Fun1); 7 Dele += new Program().Fun2; 8 9 Dele(2);10 11 Console.ReadKey();12 }13 14 static void Fun1(int a)15 {16 Console.Write(a);17 }18 void Fun2(int b)19 {20 Console.Write(b);21 }22 }
這是我們看到的代碼,對于委托只有一句:
delegate void TestDelegate(int val);或許不太好理解 委托到底是什么。
那么我們看IL代碼,圖1:

我們可以看到
命名空間 MyProject 下包含 類型 MyProject.Program
類型MyProject.Program 下分別包含:
一個類型 TestDelegate,正是我們聲明的委托。
構造函數(shù):.ctor
靜態(tài)方法:Fun1
實例方法:Fun2
程序入口:Main
由此,我們可以知道 我們聲明的委托TestDelegate是被編譯成類型的。
然后在看其內部信息:
繼承自System.MulticastDelegate
構造函數(shù):.ctor
異步方法:BeginInvoke,EndInvoke,
常規(guī)調用方法:Invoke
其中,System.MulticastDelegate 繼承自 System.Delegate。
理清上面的關系,并且把各繼承類中主要成員提取出來,于是我們上面的代碼實際是這個樣子:
1 class Program 2 { 3 /// <summary> 4 /// 委托類型,實際上System.Delegate提供了很多成員,這里只列出主要的成員。 5 /// </summary> 6 public sealed class TestDelegate 7 { 8 private object _invocationList;//Obiect類型,System.MulticastDelegate成員,委托列表,無委托列表時為null,創(chuàng)建委托列表時值為 Delegate[] 9 private object _target;//Object類型,System.Delegate成員,當以實例方法創(chuàng)建委托時,保存該實例方法的對象。當以靜態(tài)方法創(chuàng)建委托時,指向當前委托對象.10 private System.IntPtr _methodPtr;//IntPtr類型,System.Delegate成員,當以實例方法創(chuàng)建委托時,保存該實例的方法引用,運行時是該方法的內存地址。11 private System.IntPtr _methodPtrAux;//IntPtr類型,System.Delegate成員,當以靜態(tài)方法創(chuàng)建委托時,保存靜態(tài)的方法引用,運行時是該方法的內存地址。12 public System.Reflection.MethodInfo Method;//只讀屬性,返回System.Reflection.MethodInfo對象,其中包含_methodPtr或_methodPtrAux指向的方法(即注冊委托的方法)的相關信息。13 public object Target;//只讀屬性,實例方法創(chuàng)建委托 返回_target,靜態(tài)方法創(chuàng)建委托 返回null,14 ///以下主要方法的實現(xiàn)以文字描述,也方便理解。本已寫了部分偽代碼,但有些操作是編譯器實現(xiàn)的,偽代碼也不好寫。所以文字描述。15 16 /// <summary>17 /// 構造函數(shù),創(chuàng)建委托實例 18 /// </summary>19 /// <param name="target"></param>20 /// <param name="method"></param>21 protected TestDelegate(object target, string method) 22 {23 //委托類的構造函數(shù)接受兩個參數(shù),但實際上我們創(chuàng)建的時候只傳遞了一個方法引用,為什么? 24 //實際上編譯器 會分析我們傳入的參數(shù),將類型的對象引用傳遞給target,方法引用傳遞給method.25 初始化_invocationList==null;26 27 當為實例方法時:28 傳遞target 給_target29 傳遞method給_methodPtr30 31 當為靜態(tài)方法時:32 傳遞當前委托對象給_target,但此時訪問屬性Target時,返回null33 method傳遞給_methodPtrAux 34 }35 /// <summary>36 /// 添加委托37 /// </summary>38 /// <param name="a"></param>39 /// <param name="b"></param>40 /// <returns></returns>41 public static Delegate Combine(Delegate a, Delegate b) 42 {43 如果 a 和b 都為null ,拋異常44 如果a==null,返回b,b==null,返回a45 否則,合并a和b的委托列表(_invocationList),傳遞給b,返回b ;合并后,a委托列表在前,b委托列表在后 46 } 47 /// <summary>48 /// 刪除49 /// </summary>50 /// <param name="source"></param>51 /// <param name="value"></param>52 /// <returns></returns>53 public static Delegate Remove(Delegate source, Delegate value)54 {55 獲取source._invocationList56 57 如果source._invocationList 中包含value._invocationList58 從source._invocationList中移除 value._invocationList59 返回source60 61 如果value==null 或 source._invocationList 中不包含value._invocationList62 返回source63 64 如果source==null 或 source._invocationList ==value._invocationList 65 返回null66 }67 /// <summary>68 /// 調用69 /// </summary>70 /// <param name="value"></param>71 public void Invoke(int value)72 {73 如果_invocationList為null,執(zhí)行 _methodPtr.Invoke(_target,value)74 否則,遍歷_invocationList(其值為Delegate[]),調用每一個委托75 }76 }77 static void Main(string[] args)78 {79 TestDelegate Dele = new TestDelegate(Fun1);//調用構造函數(shù),F(xiàn)un1為靜態(tài)方法,此時 Dele._target指向Dele自身80 Dele += new Program().Fun2;//Fun2為實例方法,此時此時 Dele._target指向new Program()對象81 //對于這一步的+=操作的具體步驟是:(注:編譯器自動對委托實例重載了+=,-=操作,-=同理)82 //1、 new Program(),并獲取該對象Fun2方法的引用;靜態(tài)方法時,直接獲取方法引用。83 //2、 new TestDelegate(),傳入第一步方法引用為構造函數(shù)參數(shù)。84 //3、 調用Combine方法。參數(shù)分別為Dele和第二步的委托對象。85 86 Dele(2);//調用Invoke方法87 Console.ReadKey();88 }89 static void Fun1(int a)90 {91 Console.Write(a);92 }93 void Fun2(int b)94 {95 Console.Write(b);96 }97 }
委托的實質是一個類,其內部 維護了注冊方法的 類型引用,方法引用及本身的委托列表等成員。
并提供了構造,添加,刪除,調用等方法。最大的特色是可以對按順序 調用 委托列表的中注冊方法。
然后再來看事件
在上部分代碼基礎上添加事件。
1 class Program 2 { 3 public delegate void TestDelegate(int val); 4 public event TestDelegate TestEvent; 5 static void Main(string[] args) 6 { 7 Program p = new Program(); 8 p.TestEvent += p.Fun2; 9 p.TestEvent += Program.Fun1;10 p.TestEvent(3);11 Console.ReadKey();12 }13 static void Fun1(int a)14 {15 Console.Write(a);16 }17 public void Fun2(int b)18 {19 Console.Write(b);20 }21 }
直接看IL

主要成員:
1、名為TestEvent的私有TestDelegate對象
2、添加事件方法:add_TestEvent(TestDelegate value),參數(shù)為TestDelegate類型
3、刪除事件方法:remove_TestEvent(TestDelegate value),參數(shù)為TestDelegate類型
對于添加操作TestEvent+=Fun2實際會做以下操作(刪除同理):
1、獲取Fun2引用(同委托獲取引用)。
2、new TestDelegate(),并傳入第一步引用為參數(shù)。
3、調用add_TestEvent方法,參數(shù)為上一步創(chuàng)建的TestDelegate實例。
4、在add_TestEvent方法內部,通過調用System.Delegate.Combine(Delegate a, Delegate b)方法,將第二步對象加入TestEvent對象委托列表
在上述實例中就是在 Program對象 內部提前創(chuàng)建了一個私有TestDelegate委托對象TestEvent,并對其提供了 添加和刪除 TestDelegate對象的方法。
事件的添加,刪除,調用就是對TestEvent對象的添加,刪除,調用。
可以看出 所謂事件只是對委托做了簡單的包裝。其本質依然是委托。
對于常用的標準事件的寫法 public event EventHandler<EventArgs> TestEvent, 原理也如此,區(qū)別只是注冊方法的參數(shù)不同而已。
新聞熱點
疑難解答