[C#1] 9-委托
委托揭秘
編譯器和CLR在后臺做了很多工作來隱藏委托本身的復雜性,如下一句委托聲明:
//編譯器為我們產(chǎn)生了一個同名的類 public delegate void MyDelegate(int i);
看看IL:
可以看出它默認繼承自System.MulticastDelegate[所有委托都繼承此類,MulticastDelegate又繼承自System.Delegate],我們聲明的public,所以編譯器產(chǎn)生的類也是public。委托可以定義在類的內部或外部,因為委托本身就是類,所以類可以定義在哪委托就可以定義在哪。System.MulticastDelegate中有幾個重要的私有字段:
| 字段 | 類型 | 描述 |
|---|---|---|
| _target | System.object | 指向回調函數(shù)被調用時應該操作的對象,用于實例方法的回調 |
| _mothodPtr | Int32 | 一個內部的整數(shù)值,CLR用它來標識回調函數(shù) |
| _prev | System.MulticastDelegate | 指向另一個委托對象 |
所有委托都有這樣一個構造器[void .ctor (object,int)],第一個參數(shù)是一個對象的引用,第二個是一個指向回調方法的整數(shù)。聲明如下一個方法:
static void myMothod(int i); MyDelegate md=new MyDelegate(myMothod);
我們把myMothod傳給了MyDelegate的構造函數(shù),但是這和MyDelegate構造函數(shù)的參數(shù)并不匹配,但是卻編譯通過了,為什么呢?因為編譯器通過分析源代碼來確定我們引用的哪個對象和方法,上述myMothod是靜態(tài)方法,所以會把null傳遞給target參數(shù), 把一個標識方法的特殊Int32值【由MethodDef或者MethodRef元數(shù)據(jù)標記獲得】給mothodPtr參數(shù); myMothod是實例方法,則會把對象的引用賦給target。在構造器內部,這兩個參數(shù)會被保存到相應的私有字段中。 另外_prev被設置為null,該對象用來創(chuàng)建一個委托鏈表[指向下一個委托對象]。
每個委托對象實際上是對方法及其調用時操作的對象的一個封裝。 System.MulticastDelegate類有兩個只讀的共有屬性:Target和Method.當給定一個委托對象時,可以根據(jù)Target獲得一個方法回調時操作的對象引用[靜態(tài)方法返回null], Method屬性返回一個表示回調方法的System.Reflection.MethodInfo對象。
調用回調函數(shù):[ md(6);]看起來像是調用一個方法似得,并且給它一個參數(shù)6。實際上并沒有md方法,因為編譯器知道m(xù)d是一個指向委托的變量,所以他會產(chǎn)生代碼來該委托對象的Invoke方法[讓面圖片最后一行]. md(6)會被編譯為這樣一行:
IL_0014: callvirt instance void MyDelegate::Invoke(int32)
委托判等
Delegate重寫了Object的Equals方法,判斷其私有字段_target和_methodPtr字段是否指向同樣的對象和方法,相同則返回true。
MulticastDelegate又重寫了Delegate的Equals方法,它又加了一項比較,就是_prev字段。如果都為null返回ture;如果都不是null,則查看_prev字段指示的鏈表是否有指定的長度,并且兩個鏈表上的對應委托對象的_target和_methodPtr字段也是否匹配,如果匹配就返回ture。說白點就是Delegate的Equals判斷一個委托對象是否相等,MulticastDelegate的Equals則在Delegate的基礎上又增加委托鏈表的判斷。
委托鏈[_prev]:
每一個MulticastDelegate對象都有一個_prev字段,指向另一個MulticastDelegate對象的引用,則可以構成一個鏈表。Delegate有3個靜態(tài)方法來操作委托鏈表:
1 public abstract class Delegate : ICloneable, ISerializable 2 { 3 //創(chuàng)建一個由委托數(shù)組表示的委托鏈表 4 public static Delegate Combine(params Delegate[] delegates); 5 6 //組合a和b所代筆的鏈表,并返回b, 7 public static Delegate Combine(Delegate a, Delegate b); 8 9 //從source鏈表中移除和value匹配的委托【找不到匹配的也不拋異常】 10 //返回新的鏈表頭部 11 public static Delegate Remove(Delegate source, Delegate value); 12 13 }
當一個委托對象被調用時,編譯器會產(chǎn)生Invoke方法的調用。偽代碼:
public void virtual Invoke(int i) {if (_prev!=null){_prev.Invoke(i);}_target.MethodPtr(i); }
可以看出,調用一個委托對象會導致它前面的委托對象首先被調用[ _prev.Invoke(i);], 當前面委托被調用時,其返回值會被丟棄。最后才會調用自己封裝的回調目標[_target.MethodPtr(i);]; 應用程序代碼只保留了當前委托對象的哪個調用(最后一次用的回調方法)的返回值。
注意:委托對象一旦被創(chuàng)建,它們就被認為是恒定不變的,也就是說委托對象的_prev字段總是null,并且不會改變,當調用Combine將一個新委托對象加到現(xiàn)有委托鏈中時,Combine方法內部會構造一個新的委托對象,新對象有著和源對象相同的_target和_methodPtr字段,但是其_prev字段會被指向原先委托鏈表的頭部,最后Combine方法返回新委托對象的地址。
Remove方法移除一個委托對象[或者是一個委托鏈表]。[假如是你想要移除一個委托對象而不是委托鏈表] 很難看出來它到底是鏈表還是單獨的一個委托對象。最好新創(chuàng)建一個相同的委托對象,新建的委托對象的_prev字段是null,這個null很有用,如下解釋:它執(zhí)行查找委托對象[或者一個委托鏈表]時,執(zhí)行內部的一個判斷方法【Delegate的Equals方法無法判斷委托鏈表相等性,但是它又無法調用MulticastDelegate類的Equals[不知道這么說對不?], 所以就自己實現(xiàn)一個判等的方法,判等過程同MulticastDelegate類的Equals方法類似,也就是可以判斷委托鏈表相等性了,所以當你移除的一個委托對象恰好是一個委托鏈的鏈表頭部,則會把它后面指向的委托對象一起移除掉,這恐怕不是我們愿意看到的吧。
Remove方法每次都是從委托鏈表頭開始移除第一個匹配項。C#編譯器自動為委托類型提供了+=和-=操作符重載支持, 分別會調用Combine和Remove方法。
對委托鏈調用施加更多的控制
由于委托類型的Invoke方法具有調用一個委托類型對象之前的委托對象(如果存在)的能力, 但是除了最后一個回調方法的返回值外,其他回調方法的返回值都會丟失,無法得到所有回調方法的返回值。 不僅如此,如果一個被調用的委托鏈中有一個拋出了異常,或者阻塞了很久其他的委托對象將被阻止調用。 為此MulticastDelegate類提供了一個實例方法GetInvocationList,以數(shù)組的形式返回每一個委托對象, 它們的_prev字段都被設置為null,所以每個對象都是孤立的.如下小例子:
1 class Program 2 { 3 public delegate string GetMethodName(); 4 static void Main() 5 { 6 GetMethodName gmn = new GetMethodName(Method1); 7 gmn += new GetMethodName( Method2); 8 gmn += new GetMethodName( Methoh3); 9 //測試常規(guī)調用結果 10 //輸出Method3,其他兩個被丟棄 11 Console.WriteLine(gmn()); 12 13 //剛知道的Environment.NewLine,試一下,哈哈 14 Console.Write(Environment.NewLine); 15 16 Delegate[] myDelegateList = gmn.GetInvocationList(); 17 //輸出Method1 Method2 Method3 18 foreach (GetMethodName item in myDelegateList) 19 { 20 Console.WriteLine(item()); 21 } 22 23 } 24 25 public static string Method1() 26 { 27 return "Method1"; 28 } 29 public static string Method2() 30 { 31 return "Method2"; 32 } 33 public static string Methoh3() 34 { 35 return "Method3"; 36 } 37 }
轉載于:https://www.cnblogs.com/linianhui/archive/2011/04/01/csharp1_delegate.html
總結
以上是生活随笔為你收集整理的[C#1] 9-委托的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 葡萄多少钱一斤啊?
- 下一篇: 电影一般需要多少位导演