天天躁日日躁狠狠躁AV麻豆-天天躁人人躁人人躁狂躁-天天澡夜夜澡人人澡-天天影视香色欲综合网-国产成人女人在线视频观看-国产成人女人视频在线观看

把委托說(shuō)透(2):深入理解委托

上一篇隨筆中我們通過(guò)示例逐步引入了委托,并比較了委托和接口。本文將重點(diǎn)剖析委托的實(shí)質(zhì)。

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

正如很多資料上所說(shuō)的,委托是一種類型安全的函數(shù)回調(diào)機(jī)制, 它不僅能夠調(diào)用實(shí)例方法,也能調(diào)用靜態(tài)方法,并且具備按順序執(zhí)行多個(gè)方法的能力。

委托揭秘

把委托說(shuō)透(1)中可以看到,委托的使用其實(shí)是很簡(jiǎn)單的。盡管如此,其內(nèi)部實(shí)現(xiàn)仍然相當(dāng)復(fù)雜。.NET強(qiáng)大的編譯器和CLR掩蓋了這種復(fù)雜性。

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

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("注冊(cè)失敗,已經(jīng)包含名為" + user.Name + "的用戶");            }            else            {                LogDelegate("注冊(cè)成功!");            }        }    }    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類被編譯為如下形式:

image

在上圖中可以得出如下結(jié)論:

委托是一個(gè)類

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

盡管委托繼承自System.MulticastDelegate類,但我們并不能顯示地聲明一個(gè)繼承自System.MulticastDelegate類的委托。委托必須使用delegate關(guān)鍵字聲明,編譯器會(huì)自動(dòng)為我們生成繼承代碼。

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

字段名稱字段類型描述
_targetSystem.Object該字段指明委托所調(diào)用的方法所在的實(shí)例類型。如果委托調(diào)用的為靜態(tài)方法,該字段為null;如果為實(shí)例方法則為該方法所在的對(duì)象。
_methodPtrSystem.IntPtr標(biāo)識(shí)回調(diào)方法的指針。
_invocationListSystem.Object在構(gòu)建委托鏈時(shí)指向一個(gè)委托數(shù)組,在委托剛剛構(gòu)建時(shí)通常為null。

由上表可以看出,每個(gè)委托對(duì)象實(shí)際上是對(duì)方法及其調(diào)用時(shí)操作的對(duì)象的封裝。MulticastDelegate類還定義了兩個(gè)只讀公有實(shí)例屬性:Target和Method,分別對(duì)應(yīng)_target和_methodPtr。Target屬性返回一個(gè)方法回調(diào)時(shí)操作的對(duì)象引用。如果是靜態(tài)方法則返回null。Method屬性返回一個(gè)標(biāo)識(shí)回調(diào)方法的System.Reflection.MethodInfo對(duì)象。

編譯器自動(dòng)為委托創(chuàng)建了BeginInvoke、EndInvoke和Invoke三個(gè)方法

當(dāng)我們?cè)谙裾{(diào)用普通的方法一樣調(diào)用委托時(shí),如

LogDelegate("注冊(cè)失敗,已經(jīng)包含名為" + user.Name + "的用戶");

這時(shí)實(shí)際上調(diào)用的是編譯器自動(dòng)生成的Invoke方法

LogDelegate.Invoke("注冊(cè)失敗,已經(jīng)包含名為" + user.Name + "的用戶");

使用IL DASM查看UserService的IL代碼,可以驗(yàn)證以上結(jié)論,如下圖所示:

image 在使用委托時(shí),我們也可以顯示調(diào)用Invoke方法(CLR 2.0)。

image

Invoke方法的參數(shù)和返回值與委托是一致的。在調(diào)用Invoke方法時(shí),會(huì)使用_target和_methodPtr字段。

BeginInvoke和EndInvoke方法用來(lái)實(shí)現(xiàn)異步調(diào)用,本文在此不進(jìn)行討論。

委托鏈

委托鏈?zhǔn)且粋€(gè)委托的集合,它允許我們調(diào)用這個(gè)集合中的委托所代表的所有方法。在Delegate類中定義了3個(gè)靜態(tài)方法來(lái)幫助我們操作委托鏈。

public static Delegate Combine(params Delegate[] delegates);public static Delegate Combine(Delegate a, Delegate b);public static Delegate Remove(Delegate source, Delegate value);

要理解委托鏈,我們首先基于前面的例子,重新聲明兩個(gè)委托:logDel1和logDel2。

Log logDel1 = LogToConsole;Program p = new Program();Log logDel2 = p.LogToTextFile;

這兩個(gè)委托的_target、_methodPtr和_invocationList值分別如下圖所示:

image

構(gòu)造委托鏈

然后,我們使用Combin方法來(lái)構(gòu)造一個(gè)委托鏈:

Log logChain = null;logChain = (Log)Delegate.Combine(logChain, logDel1);

由于logChain初始為null,在使用Combin方法構(gòu)造委托鏈時(shí),將返回另外一個(gè)參數(shù)logDel1,再將logDel1的引用賦給logChain。這時(shí)logChain將指向logDel1所指向的對(duì)象。

image

接下來(lái)我們將logDel2也添加到logChain中來(lái):

logChain = (Log)Delegate.Combine(logChain, logDel2);

此時(shí),由于logChain已經(jīng)不再是null,將重新構(gòu)建一個(gè)新的委托對(duì)象。該委托對(duì)象的_target和_methodPtr字段與logDel2(第二個(gè)參數(shù))相同,_invocationList字段將指向一個(gè)委托數(shù)組。該委托數(shù)組中包含兩個(gè)元素,第一個(gè)元素(索引為0)指向封裝了LogToConsole方法的委托(即logDel1指向的委托);第二個(gè)元素(索引為1)指向封裝了LogToTextFile方法的委托(即logDel2指向的委托)。最后,將這個(gè)新創(chuàng)建的委托對(duì)象的引用賦給logChain。

image

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

至此,委托鏈構(gòu)造完畢,我們來(lái)看看如何執(zhí)行委托鏈表中的委托。由于logChain仍然指向一個(gè)委托對(duì)象,因此執(zhí)行委托鏈表的語(yǔ)法與執(zhí)行委托是一樣的:

logChain("執(zhí)行委托鏈");

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

委托Log的Invoke方法的實(shí)現(xiàn)用偽代碼表示如下:

public void Invoke(string message){     Delegate[] delegateSet = _InvocationList as Delegate[];    if (delegateSet != null)     {        // 如果委托數(shù)組不為空,則依次執(zhí)行該委托數(shù)組中的委托        foreach (Feedback d in delegateSet)            d(value);    }     else     {        // 如果委托數(shù)組為空,則該委托不代表一個(gè)委托鏈        // 按照正常方式執(zhí)行該委托        _methodPtr.Invoke(_target, value);    }}

 

包含返回值的委托的Invoke實(shí)現(xiàn)如下,假設(shè)返回值為string:

public void Invoke(string message){    string result = null;    Delegate[] delegateSet = _InvocationList as Delegate[];    if (delegateSet != null)    {        // 如果委托數(shù)組不為空,則依次執(zhí)行該委托數(shù)組中的委托        foreach (Feedback d in delegateSet)            result = d(value);    }    else    {        // 如果委托數(shù)組為空,則該委托不代表一個(gè)委托鏈        // 按照正常方式執(zhí)行該委托        result = _methodPtr.Invoke(_target, value);    }    return result;}

可以看到在委托鏈中,返回值為鏈表中最后一個(gè)委托的返回值

那么如果對(duì)兩個(gè)委托鏈調(diào)用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);

最終的結(jié)果是,logChain的_target和_methodPtr均與logDel4相同(確切地說(shuō),兩個(gè)委托對(duì)象的_methodPtr字段并不相同,但Method屬性是相同的),而_invocationList中委托的順序依次為logDel1、logDel2、logDel3、logDel4。

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

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

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

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

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

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

    (3)_invocationList字段為一個(gè)委托數(shù)組,該數(shù)組中委托的順序?yàn)椋篈中_invacationList所指向的委托數(shù)組 + B中_invacationList所指向的委托數(shù)組。

移除委托鏈

Combine方法用來(lái)向委托鏈中添加一個(gè)委托,而Remove方法用來(lái)從委托鏈中移除一個(gè)委托。

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

當(dāng)調(diào)用Remove時(shí),會(huì)遍歷(倒序)第一個(gè)參數(shù)(logChain)中的中的委托列表(_invocationList字段), 找到與第二個(gè)參數(shù)(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的鏈表中只剩下一個(gè)委托,則返回該委托。

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

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

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

總結(jié)

本文首先介紹了委托的實(shí)質(zhì),委托是一個(gè)類,它繼承自System.MulticastDelegate,而MulticastDelegate又繼承自System.Delegate。然后重點(diǎn)剖析了委托鏈,討論了如何創(chuàng)建和移除委托鏈。

在接下來(lái)的隨筆中,我們將對(duì).NET中委托的一個(gè)典型應(yīng)用——事件,進(jìn)行全面深入的介紹。

參考資料

CLR via C# 2nd Edition

NET技術(shù)把委托說(shuō)透(2):深入理解委托,轉(zhuǎn)載需保留來(lái)源!

鄭重聲明:本文版權(quán)歸原作者所有,轉(zhuǎn)載文章僅為傳播更多信息之目的,如作者信息標(biāo)記有誤,請(qǐng)第一時(shí)間聯(lián)系我們修改或刪除,多謝。

主站蜘蛛池模板: 色老头色老太aaabbb | 亚洲国产夜色在线观看 | 深夜释放自己在线观看 | 精品视频在线观看视频免费视频 | 天堂so导航 | 久久人妻无码毛片A片麻豆 久久人妻熟女中文字幕AV蜜芽 | 性色少妇AV蜜臀人妻无码 | 欧美日韩中文国产一区 | xhameter中国| 果冻传媒2021精品在线观看 | 偷拍亚洲制服另类无码专区 | 国产AV精品一区二区三区漫画 | 久久精品国产久精国产果冻传媒 | 国语自产视频在线不卡 | 荡乳乱公小说 | 免费精品一区二区三区在线观看 | 很很射影院 | 免费亚洲视频 | 精品人妻伦一二三区久久AAA片 | 亚洲欧美自拍清纯中文字幕 | 国产在线观看黄 | 色欲AV蜜臀AV在线观看麻豆 | 啊好深啊别拔就射在里面 | 久久这里只有精品国产精品99 | 欧美日韩高清一区二区三区 | 亚洲永久精品AV在线观看 | younv 学生国产在线视频 | 伊人AV一区二区三区夜色撩人 | 热久久视久久精品2015 | 精品国产乱码久久久久久免费 | 午夜阳光影院在线观看视频 | 久久精品国产亚洲AV妓女不卡 | 男女作爱在线播放免费网页版观看 | 国产中文字幕乱码免费 | 国产精品久久久久久久久无码 | 亚洲精品无码不卡在线播放he | 天天色狠狠干 | 日本高清免费在线观看 | 久久精品国产欧美 | 青草久久精品亚洲综合专区 | 三级网址在线播放 |