亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 學院 > 開發設計 > 正文

深入理解C#委托的實質

2019-11-17 04:08:14
字體:
來源:轉載
供稿:網友
本文是博客園麒麟.NET的《把委托說透》系列的第二篇,重點剖析C#委托的實質。

委托在本質上仍然是一個類,我們用delegate關鍵字聲明的所有委托都繼承自System.MulticastDelegate。后者又是繼承自System.Delegate類,System.Delegate類則繼承自System.Object。委托既然是一個類,那么它就可以被定義在任何地方,即可以定義在類的內部,也可以定義在類的外部。

正如很多資料上所說的,委托是一種類型安全的函數回調機制, 它不僅能夠調用實例方法,也能調用靜態方法,并且具備按順序執行多個方法的能力。

C#委托揭秘

在把委托說透(1)中可以看到,委托的使用其實是很簡單的。盡管如此,其內部實現仍然相當復雜。.NET強大的編譯器和CLR掩蓋了這種復雜性。

為了解釋方便,我們把(1)中的委托代碼復制在下面,并做一處小小的改動,將LogToTextFile設置為實例方法。

namespace DelegateSample  {       public delegate void Log(string message);       class UserService      {          public Log LogDelegate { get; set; }           public UserService() { }           public void Register(User user)          {              if (user.Name == "Kirin")              {                  LogDelegate("注冊失敗,已經包含名為" + user.Name + "的用戶");              }              else             {                  LogDelegate("注冊成功!");              }          }      }       class PRogram      {          static void Main(string[] args)          {              User user = new User { Name = "Kirin", PassWord = "123" };              UserService service = new UserService();              service.LogDelegate = LogToConsole;              Program p = new Program();              service.LogDelegate += p.LogToTextFile;              service.Register(user);               Console.ReadLine();          }           static void LogToConsole(string message)          {              Console.WriteLine(message);          }           void LogToTextFile(string message)          {              using (StreamWriter sw = File.AppendText("log.txt"))              {                  sw.WriteLine(message);                  sw.Flush();                  sw.Close();              }          }      }  }   打開Reflector反編譯Log委托,可以看到Log類被編譯為如下形式:



在上圖中可以得出如下結論:

委托是一個類

可以很清晰的看出Log—>MulticastDelegate—>Delegate這種繼承機制。

盡管委托繼承自System.MulticastDelegate類,但我們并不能顯示地聲明一個繼承自System.MulticastDelegate類的委托。委托必須使用delegate關鍵字聲明,編譯器會自動為我們生成繼承代碼。

由于委托繼承自System.MulticastDelegate類,自然也繼承MulticastDelegate類的字段、屬性和方法。這些成員中,最重要的當屬三個非公共字段,如下表所示:


字段名稱 字段類型 描述
_target System.Object 該字段指明委托所調用的方法所在的實例類型。如果委托調用的為靜態方法,該字段為null;如果為實例方法則為該方法所在的對象。
_methodPtr System.IntPtr 標識回調方法的指針。
_invocationList System.Object 在構建委托鏈時指向一個委托數組,在委托剛剛構建時通常為null。


由上表可以看出,每個委托對象實際上是對方法及其調用時操作的對象的封裝。MulticastDelegate類還定義了兩個只讀公有實例屬性:Target和Method,分別對應_target和_methodPtr。Target屬性返回一個方法回調時操作的對象引用。如果是靜態方法則返回null。Method屬性返回一個標識回調方法的System.Reflection.MethodInfo對象。

編譯器自動為委托創建了BeginInvoke、EndInvoke和Invoke三個方法

當我們在像調用普通的方法一樣調用委托時,如

LogDelegate("注冊失敗,已經包含名為" + user.Name + "的用戶"); 這時實際上調用的是編譯器自動生成的Invoke方法

LogDelegate.Invoke("注冊失敗,已經包含名為" + user.Name + "的用戶"); 使用IL DASM查看UserService的IL代碼,可以驗證以上結論,如下圖所示:



在使用委托時,我們也可以顯示調用Invoke方法(CLR 2.0)。



Invoke方法的參數和返回值與委托是一致的。在調用Invoke方法時,會使用_target和_methodPtr字段。

BeginInvoke和EndInvoke方法用來實現異步調用,本文在此不進行討論。

委托鏈

委托鏈是一個委托的集合,它允許我們調用這個集合中的委托所代表的所有方法(對于有返回值的方法,委托鏈的返回值為鏈表中最后一個方法的返回值,本文后面會有詳細介紹)。在Delegate類中定義了3個靜態方法來幫助我們操作委托鏈。

public static Delegate Combine(params Delegate[] delegates);  public static Delegate Combine(Delegate a, Delegate b);  public static Delegate Remove(Delegate source, Delegate value);  要理解委托鏈,我們首先基于前面的例子,重新聲明兩個委托:logDel1和logDel2。

Log logDel1 = LogToConsole;  Program p = new Program();  Log logDel2 = p.LogToTextFile;  這兩個委托的_target、_methodPtr和_invocationList值分別如下圖所示:



構造委托鏈

然后,我們使用Combin方法來構造一個委托鏈:

Log logChain = null;  logChain = (Log)Delegate.Combine(logChain, logDel1);  由于logChain初始為null,在使用Combin方法構造委托鏈時,將返回另外一個參數logDel1,再將logDel1的引用賦給logChain。這時logChain將指向logDel1所指向的對象。



接下來我們將logDel2也添加到logChain中來:

logChain = (Log)Delegate.Combine(logChain, logDel2); 此時,由于logChain已經不再是null,將重新構建一個新的委托對象。該委托對象的_target和_methodPtr字段與logDel2(第二個參數)相同,_invocationList字段將指向一個委托數組。該委托數組中包含兩個元素,第一個元素(索引為0)指向封裝了LogToConsole方法的委托(即logDel1指向的委托);第二個元素(索引為1)指向封裝了LogToTextFile方法的委托(即logDel2指向的委托)。最后,將這個新創建的委托對象的引用賦給logChain。



若再將一個新的委托logDel3添加到委托鏈中,則仍然會構建一個新的委托對象,并將logDel3的引用添加到該委托對象_invocationList的末尾(此時鏈表共有3個元素)。然后,再將該委托對象的引用賦給logChain。而logChain之前指向的委托對象則等待垃圾回收。

至此,委托鏈構造完畢,我們來看看如何執行委托鏈表中的委托。由于logChain仍然指向一個委托對象,因此執行委托鏈表的語法與執行委托是一樣的:

logChain("執行委托鏈"); 與普通的委托(如logDel1)所不同的是,logChain的_invocationList字段不為null。這時將首先遍歷執行_invocationList中的所有委托。所執行的方法的順序與添加的順序一致,依次為LogToConsole、LogToTextFile。

委托Log的Invoke方法的實現用偽代碼表示如下:

public void Invoke(string message)  {       Delegate[] delegateSet = _InvocationList as Delegate[];      if (delegateSet != null)       {          // 如果委托數組不為空,則依次執行該委托數組中的委托          foreach (Feedback d in delegateSet)              d(value);      }       else       {          // 如果委托數組為空,則該委托不代表一個委托鏈          // 按照正常方式執行該委托          _methodPtr.Invoke(_target, value);      }  }  包含返回值的委托的Invoke實現如下,假設返回值為string:

public string Invoke(string message)  {      string result = null;      Delegate[] delegateSet = _InvocationList as Delegate[];      if (delegateSet != null)      {          // 如果委托數組不為空,則依次執行該委托數組中的委托          foreach (Feedback d in delegateSet)              result = d(value);      }      else     {          // 如果委托數組為空,則該委托不代表一個委托鏈          // 按照正常方式執行該委托          result = _methodPtr.Invoke(_target, value);      }      return result;  }  可以看到在委托鏈中,返回值為鏈表中最后一個委托的返回值。

那么如果對兩個委托鏈調用Combine方法呢?

Log logChain = null;  Log logChain1 = null;  Log logChain2 = null;  logChain1 = (Log)Delegate.Combine(logChain1, logDel1);  logChain1 = (Log)Delegate.Combine(logChain1, logDel2);  logChain2 = (Log)Delegate.Combine(logChain2, logDel3;  logChain2 = (Log)Delegate.Combine(logChain2, logDel4;  logChain = (Log)Delegate.Combine(logChain1, logChain2);  最終的結果是,logChain的_target和_methodPtr均與logDel4相同(確切地說,兩個委托對象的_methodPtr字段并不相同,但Method屬性是相同的),而_invocationList中委托的順序依次為logDel1、logDel2、logDel3、logDel4。

綜上所述,可以對Delegate.Combine(Delegate A, Delegate B)方法做如下總結:

1. 如果A和B均為null,則返回null。

2. 如果A或B一個為null而另一個不為null,則返回不為null的委托。

3. 如果A和B均不為null,返回一個新的委托,該委托

(1)_target字段與B的_target字段的值相同

(2)Method屬性與B的Method屬性的值相同

(3)_invocationList字段為一個委托數組,該數組中委托的順序為:A中_invacationList所指向的委托數組 + B中_invacationList所指向的委托數組。

移除委托鏈

Combine方法用來向委托鏈中添加一個委托,而Remove方法用來從委托鏈中移除一個委托。

logChain = (Log)Delegate.Remove(logChain, new Log(LogToConsole));

當調用Remove時,會遍歷(倒序)第一個參數(logChain)中的中的委托列表(_invocationList字段), 找到與第二個參數(new Log(LogToConsole))的_target和_methodPtr字段相匹配的委托,并將其從委托列表中移除。返回值需分以下幾種情況,為了描述方便,我們將logChain記為A,將new Log(LogToConsole)記為B。

1. 如果A為null,返回null。

2. 如果B為null,返回A。

3. 如果A的_invocationList為null,即不包含委托鏈,那么如果A本身與B匹配,則返回null,否則返回A。

4. 如果A的_invocationList中不包含與B匹配的委托,則返回A。

5. 如果A的_invocationList中包含與B匹配的委托,則從鏈表中移除B,然后

(1)如果A的鏈表中只剩下一個委托,則返回該委托。

(2)如果A的鏈表中還剩下多個委托,將重新構建一個新的委托R(R的_invocationList字段為A的_invocationList移除了B之后的鏈表),并返回R。

注意,Remove方法只移除源委托的_invocationList列表中第一個匹配的委托,要想移除所有匹配的委托,可以使用RemoveAll方法。

有了委托鏈,在(1)中提出的第二個疑問就迎刃而解了。當用戶希望使用多種日志記錄方式的時候,使用委托鏈可以輕松地添加和刪除某種日志記錄方式,從而避免了人為地維護一個列表。

總結

本文首先介紹了C#委托的實質,委托是一個類,它繼承自System.MulticastDelegate,而MulticastDelegate又繼承自System.Delegate。然后重點剖析了委托鏈,討論了如何創建和移除委托鏈。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
国产精品美女无圣光视频| 国产免费一区二区三区在线观看| 亚洲精品免费av| 91国偷自产一区二区三区的观看方式| 国内免费久久久久久久久久久| 久久资源免费视频| 成人黄色短视频在线观看| 国产91在线播放| 国内精品在线一区| 亚洲色图美腿丝袜| 国产99视频在线观看| 91sao在线观看国产| 91av在线播放| 成人免费视频网| 青青草国产精品一区二区| 久久久免费观看视频| 亚洲国产欧美精品| 日韩精品欧美激情| 国产精品最新在线观看| www国产精品com| 日韩最新中文字幕电影免费看| 91视频88av| 久久久久久久爱| 国产日韩欧美夫妻视频在线观看| 成人免费观看a| 欧美孕妇孕交黑巨大网站| 中文字幕久久亚洲| 中文字幕欧美日韩| 欧美激情精品久久久久久黑人| 亚洲精品黄网在线观看| 日韩美女激情视频| 欧美激情综合色综合啪啪五月| 久久久精品国产一区二区| 国产小视频国产精品| 久久精品小视频| 亚洲精品国产福利| 91久久精品日日躁夜夜躁国产| 亚洲精品日韩久久久| 国产精品扒开腿爽爽爽视频| 国产伦精品免费视频| 久久亚洲欧美日韩精品专区| 国产精品扒开腿做爽爽爽视频| 热99久久精品| 国产69久久精品成人看| 成人女保姆的销魂服务| 国产视频欧美视频| 亚洲精品欧美日韩| 欧美人成在线视频| 国产精品69精品一区二区三区| 欧美成人激情视频免费观看| 国产成人精品免高潮费视频| 91精品综合久久久久久五月天| 亚洲国产高清高潮精品美女| 精品国产福利在线| 午夜精品www| 久久影视三级福利片| 色婷婷综合成人av| 亚洲人成毛片在线播放| 九九精品视频在线| 亚洲人成网站色ww在线| 日韩精品在线免费| 92国产精品视频| 亚洲成人久久一区| 激情亚洲一区二区三区四区| 亚洲在线第一页| 国产精品免费一区豆花| 欧美一区二区视频97| 久久精品91久久久久久再现| 久久成人人人人精品欧| 亚洲黄色av网站| 久久久久久九九九| 午夜精品视频网站| 亚洲国产成人精品一区二区| 日韩在线视频免费观看高清中文| 成人精品在线视频| 日韩精品丝袜在线| 国产一区二区成人| 国产成人精品一区| 亚洲美女喷白浆| 精品亚洲一区二区| 国产精品久久久久久久久久免费| xvideos成人免费中文版| 亚洲国产精品美女| 中文字幕日韩欧美精品在线观看| 亚洲激情视频在线| 日韩一级黄色av| 亚洲第一在线视频| 国产精品一区二区久久精品| 日韩av电影手机在线| 亚洲精品在线91| 中文欧美日本在线资源| 国产精品7m视频| 欧美成人免费观看| 国产精品精品视频一区二区三区| 国产高清视频一区三区| 九九热精品视频国产| 国产精品视频资源| 亚洲天堂影视av| www亚洲精品| 欧美日韩黄色大片| 国产视频在线一区二区| 久久久久久999| 欧美激情xxxx性bbbb| 国产精品电影一区| 久久天天躁狠狠躁夜夜av| 亚洲免费电影一区| 亚洲成色777777女色窝| 亚洲成**性毛茸茸| 亚洲欧美日韩在线高清直播| 中文字幕亚洲欧美在线| 成人h片在线播放免费网站| 中文字幕成人精品久久不卡| 欧美日韩亚洲精品内裤| 最近2019年日本中文免费字幕| 国产精品99免视看9| 中文字幕亚洲激情| 岛国av一区二区在线在线观看| 国产z一区二区三区| 精品无人区乱码1区2区3区在线| 日韩美女免费线视频| 青草青草久热精品视频在线网站| 久久久久久久久久久免费精品| 久久中文字幕视频| 久久精品视频网站| 最近中文字幕日韩精品| 亚洲欧美综合区自拍另类| 国产精品最新在线观看| 久久国产精品久久精品| 亚州欧美日韩中文视频| 日韩电影在线观看中文字幕| 国产美女精彩久久| 国产美女精品免费电影| 亚洲深夜福利视频| 国产精自产拍久久久久久| 欧美在线视频在线播放完整版免费观看| 91精品国产色综合久久不卡98口| 欧美日韩久久久久| 日韩精品视频在线观看免费| 亚洲免费人成在线视频观看| 亚洲国产精品人人爽夜夜爽| www.久久久久久.com| 国产剧情久久久久久| 成人免费网站在线| 久久频这里精品99香蕉| 国产一区二区精品丝袜| 亚洲欧美中文另类| 欧美一级视频一区二区| 2019中文字幕全在线观看| 亚洲第一级黄色片| 国产精品视频播放| 日韩在线视频一区| 亚洲成人xxx| 欧美国产日韩xxxxx| 搡老女人一区二区三区视频tv| 狠狠综合久久av一区二区小说| 国产精品精品国产| 97精品伊人久久久大香线蕉| 伊人一区二区三区久久精品| 中文字幕久精品免费视频| 亚洲一级黄色片| 精品美女久久久久久免费| 国产日产久久高清欧美一区| 国产精品99导航| 国产日韩中文字幕|