偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

那些編譯器藏的和U3D給的

移動(dòng)開(kāi)發(fā)
本文主要的內(nèi)容集中在了委托的使用以及內(nèi)部結(jié)構(gòu)(當(dāng)然還有事件了,但是受制于篇幅故分為兩篇文章)以及結(jié)合一部分Unity3D的設(shè)計(jì)思考。當(dāng)然由于時(shí)間倉(cāng)促,文中難免有一些疏漏和不準(zhǔn)確,也歡迎各位指出,共同進(jìn)步。

[[145299]]

0x00 前言

由于工作繁忙所以距離上一篇博客已經(jīng)過(guò)去一個(gè)多月的時(shí)間了,因此決心這個(gè)周末無(wú)論如何也得寫(xiě)點(diǎn)東西出來(lái),既是總結(jié)也是分享。那么本文主要的內(nèi)容集中在了委托的使用以及內(nèi)部結(jié)構(gòu)(當(dāng)然還有事件了,但是受制于篇幅故分為兩篇文章)以及結(jié)合一部分Unity3D的設(shè)計(jì)思考。當(dāng)然由于時(shí)間倉(cāng)促,文中難免有一些疏漏和不準(zhǔn)確,也歡迎各位指出,共同進(jìn)步。

0x01 從觀察者模式說(shuō)起

在設(shè)計(jì)模式中,有一種我們常常會(huì)用到的設(shè)計(jì)模式——觀察者模式。那么這種設(shè)計(jì)模式和我們的主題“如何在Unity3D中使用委托”有什么關(guān)系呢?別急,先讓我們來(lái)聊一聊什么是觀察者模式。

首先讓我們來(lái)看看報(bào)紙和雜志的訂閱是怎么一回事:

  1. 報(bào)社的任務(wù)便是出版報(bào)紙。
  2. 向某家報(bào)社訂閱他們的報(bào)紙,只要他們有新的報(bào)紙出版便會(huì)向你發(fā)放。也就是說(shuō),只要你是他們的訂閱客戶(hù),便可以一直收到新的報(bào)紙。
  3. 如果不再需要這份報(bào)紙,則可以取消訂閱。取消之后,報(bào)社便不會(huì)再送新的報(bào)紙過(guò)來(lái)。
  4. 報(bào)社和訂閱者是兩個(gè)不同的主體,只要報(bào)社還一直存在著,不同的訂閱者便可以來(lái)訂閱或取消訂閱。

如果各位讀者能看明白我上面所說(shuō)的報(bào)紙和雜志是如何訂閱的,那么各位也就了解了觀察者模式到底是怎么一回事。除了名稱(chēng)不大一樣,在觀察者模式中,報(bào)社或者說(shuō)出版者被稱(chēng)為“主題”(Subject),而訂閱者則被稱(chēng)為“觀察者”(Observer)。將上面的報(bào)社和訂閱者的關(guān)系移植到觀察者模式中,就變成了如下這樣:主題(Subject)對(duì)象管理某些數(shù)據(jù),當(dāng)主題內(nèi)的數(shù)據(jù)改變時(shí),便會(huì)通知已經(jīng)訂閱(注冊(cè))的觀察者,而已經(jīng)注冊(cè)主題的觀察者此時(shí)便會(huì)收到主題數(shù)據(jù)改變的通知并更新,而沒(méi)有注冊(cè)的對(duì)象則不會(huì)被通知。

當(dāng)我們?cè)噲D去勾勒觀察者模式時(shí),可以使用報(bào)紙訂閱服務(wù),或者出版者和訂閱者來(lái)比擬。而在實(shí)際的開(kāi)發(fā)中,觀察者模式被定義為了如下這樣:

觀察者模式:定義了對(duì)象之間的一對(duì)多依賴(lài),這樣一來(lái),當(dāng)一個(gè)對(duì)象改變狀態(tài)時(shí),它的所有依賴(lài)者都會(huì)收到通知并自動(dòng)更新。

那么介紹了這么多觀察者模式,是不是也該說(shuō)一說(shuō)委托了呢?是的,C#語(yǔ)言通過(guò)委托來(lái)實(shí)現(xiàn)回調(diào)函數(shù)的機(jī)制,而回調(diào)函數(shù)是一種很有用的編程機(jī)制,可以被廣泛的用在觀察者模式中。

那么Unity3D本身是否有提供這種機(jī)制呢?答案也是肯定的,那么和委托又有什么區(qū)別呢?下面就讓我們來(lái)聊一聊這個(gè)話題。

0x02 向Unity3D中的SendMessage和BroadcastMessage說(shuō)拜拜

當(dāng)然,不可否認(rèn)Unity3D游戲引擎的出現(xiàn)是游戲開(kāi)發(fā)者的一大福音。但不得不說(shuō)的是,Unity3D的游戲腳本的架構(gòu)中是存在一些缺陷的。一個(gè)很好的例子就是本節(jié)要說(shuō)的圍繞SendMessage和BroadcastMessage而構(gòu)建的消息系統(tǒng)。之所以說(shuō)Unity3D的這套消息系統(tǒng)存在缺陷,主要是由于SendMessage和BroadcastMessage過(guò)于依賴(lài)反射機(jī)制(reflection)來(lái)查找消息對(duì)應(yīng)的回調(diào)函數(shù)。頻繁的使用反射自然會(huì)影響性能,但是性能的損耗還并非最為嚴(yán)重的問(wèn)題,更加嚴(yán)重的問(wèn)題是使用這種機(jī)制之后代碼的維護(hù)成本。為什么說(shuō)這樣做是一個(gè)很糟糕的事情呢?因?yàn)槭褂米址畞?lái)標(biāo)識(shí)一個(gè)方法可能會(huì)導(dǎo)致很多隱患的出現(xiàn)。舉一個(gè)例子:假如開(kāi)發(fā)團(tuán)隊(duì)中某個(gè)開(kāi)發(fā)者決定要重構(gòu)某些代碼,很不巧,這部分代碼便是那些可能要被這些消息調(diào)用的方法定義的代碼,那么如果方法被重新命名甚至被刪除,是否會(huì)導(dǎo)致很?chē)?yán)重的隱患呢?答案是yes。這種隱患的可怕之處并不在于可能引發(fā)的編譯時(shí)錯(cuò)誤,恰恰相反,這種隱患的可怕之處在于編譯器可能都不會(huì)報(bào)錯(cuò)來(lái)提醒開(kāi)發(fā)者某些方法已經(jīng)被改名甚至是不存在了,面對(duì)一個(gè)能夠正常的運(yùn)行程序而沒(méi)有警覺(jué)是最可怕的,而什么時(shí)候這個(gè)隱患會(huì)爆發(fā)呢?就是觸發(fā)了特定的消息而找不到對(duì)應(yīng)的方法的時(shí)候 ,但這時(shí)候發(fā)現(xiàn)問(wèn)題所在往往已經(jīng)太遲了。

另一個(gè)潛在的問(wèn)題是由于使用了反射機(jī)制因而Unity3D的這套消息系統(tǒng)也能夠調(diào)用聲明為私有的方法的。但是如果一個(gè)私有方法在聲明的類(lèi)的內(nèi)部沒(méi)有被使用,那么正常的想法肯定都認(rèn)為這是一段廢代碼,因?yàn)樵谶@個(gè)類(lèi)的外部不可能有人會(huì)調(diào)用它。那么對(duì)待廢代碼的態(tài)度是什么呢?我想很多開(kāi)發(fā)者都會(huì)選擇消滅這段廢代碼,那么同樣的隱患又會(huì)出現(xiàn),可能在編譯時(shí)并沒(méi)有問(wèn)題,甚至程序也能正常運(yùn)行一段時(shí)間,但是只要觸發(fā)了特定的消息而沒(méi)有對(duì)應(yīng)的方法,那便是這種隱患爆發(fā)的時(shí)候。因而,是時(shí)候向Unity3D中的SendMessage和BroadcastMessage說(shuō)拜拜了,讓我們選擇C#的委托來(lái)實(shí)現(xiàn)自己的消息機(jī)制吧。

0x03 認(rèn)識(shí)回調(diào)函數(shù)機(jī)制----委托

在非托管代碼C/C++中也存在類(lèi)似的回調(diào)機(jī)制,但是這些非成員函數(shù)的地址僅僅是一個(gè)內(nèi)存地址。而這個(gè)地址并不攜帶任何額外的信息,例如函數(shù)的參數(shù)個(gè)數(shù)、參數(shù)類(lèi)型、函數(shù)的返回值類(lèi)型,因而我們說(shuō)非托管C/C++代碼的回調(diào)函數(shù)不是類(lèi)型安全的。而C#中提供的回調(diào)函數(shù)的機(jī)制便是委托,一種類(lèi)型安全的機(jī)制。為了直觀的了解委托,我們先來(lái)看一段代碼:

  1. using UnityEngine; 
  2.  
  3. using System.Collections; 
  4.  
  5.   
  6.  
  7.   
  8.  
  9. public class DelegateScript : MonoBehaviour 
  10.  
  11. {   
  12.  
  13.     //聲明一個(gè)委托類(lèi)型,它的實(shí)例引用一個(gè)方法 
  14.  
  15.     internal delegate void MyDelegate(int num); 
  16.  
  17.     MyDelegate myDelegate; 
  18.  
  19.     
  20.  
  21.   
  22.  
  23.     void Start () 
  24.  
  25.     { 
  26.  
  27.         //委托類(lèi)型MyDelegate的實(shí)例myDelegate引用的方法 
  28.  
  29.         //是PrintNum 
  30.  
  31.         myDelegate = PrintNum; 
  32.  
  33.         myDelegate(50); 
  34.  
  35.         //委托類(lèi)型MyDelegate的實(shí)例myDelegate引用的方法 
  36.  
  37.         //DoubleNum        
  38.  
  39.         myDelegate = DoubleNum; 
  40.  
  41.         myDelegate(50); 
  42.  
  43.     } 
  44.  
  45.     
  46.  
  47.     void PrintNum(int num) 
  48.  
  49.     { 
  50.  
  51.         Debug.Log ("Print Num: " + num); 
  52.  
  53.     } 
  54.  
  55.     
  56.  
  57.     void DoubleNum(int num) 
  58.  
  59.     { 
  60.  
  61.         Debug.Log ("Double Num: " + num * 2); 
  62.  
  63.     } 
  64.  
下面我們來(lái)看看這段代碼做的事情。在最開(kāi)始,我們可以看到internal委托類(lèi)型MyDelegate的聲明。委托要確定一個(gè)回調(diào)方法簽名,包括參數(shù)以及返回類(lèi)型等等,在本例中MyDelegate委托制定的回調(diào)方法的參數(shù)類(lèi)型是int型,同時(shí)返回類(lèi)型為void。

DelegateScript類(lèi)還定義了兩個(gè)私有方法PrintNum和DoubleNum,它們的分別實(shí)現(xiàn)了打印傳入的參數(shù)和打印傳入的參數(shù)的兩倍的功能。在Start方法中,MyDelegate類(lèi)的實(shí)例myDelegate分別引用了這兩個(gè)方法,并且分別調(diào)用了這兩個(gè)方法。

看到這里,不知道各位讀者是否會(huì)產(chǎn)生一些疑問(wèn),為什么一個(gè)方法能夠像這樣myDelegate = PrintNum; “賦值”給一個(gè)委托呢?這便不得不提C#2為委托提供的方法組轉(zhuǎn)換?;厮軨#1的委托機(jī)制,也就是十分原始的委托機(jī)制中,如果要?jiǎng)?chuàng)建一個(gè)委托實(shí)例就必須要同時(shí)指定委托類(lèi)型和要調(diào)用的方法(執(zhí)行的操作),因而剛剛的那行代碼就要被改為:

  1. new MyDelegate(PrintNum); 

即便回到C#1的時(shí)代,這行創(chuàng)建新的委托實(shí)例的代碼看上去似乎并沒(méi)有讓開(kāi)發(fā)者產(chǎn)生什么不好的印象,但是如果是作為較長(zhǎng)的一個(gè)表達(dá)式的一部分時(shí),就會(huì)讓人感覺(jué)很冗繁了。一個(gè)明顯的例子是在啟動(dòng)一個(gè)新的線程時(shí)候的表達(dá)式:

  1. Thread th = new Thread(new ThreadStart(Method)); 

這樣看起來(lái),C#1中的方式似乎并不簡(jiǎn)潔。因而C#2為委托引入了方法組轉(zhuǎn)換機(jī)制,即支持從方法到兼容的委托類(lèi)型的隱式轉(zhuǎn)換。就如同我們一開(kāi)始的例子中做的那樣。

  1. //使用方法組轉(zhuǎn)換時(shí),隱式轉(zhuǎn)換會(huì)將 
  2.  
  3. //一個(gè)方法組轉(zhuǎn)換為具有兼容簽名的 
  4.  
  5. //任意委托類(lèi)型 
  6.  
  7. myDelegate = PrintNum; 
  8.  
  9. Thread th = new Thread(Method); 

而這套機(jī)制之所以叫方法組轉(zhuǎn)換,一個(gè)重要的原因就是由于重載,可能不止一個(gè)方法適用。例如下面這段代碼所演示的那樣:

  1. using UnityEngine; 
  2.  
  3. using System.Collections; 
  4.  
  5.  
  6. public class DelegateScript : MonoBehaviour 
  7.  
  8. {   
  9.  
  10.     //聲明一個(gè)委托類(lèi)型,它的實(shí)例引用一個(gè)方法 
  11.  
  12. delegate void MyDelegate(int num); 
  13.  
  14.     //聲明一個(gè)委托類(lèi)型,它的實(shí)例引用一個(gè)方法 
  15.  
  16.     delegate void MyDelegate2(int num, int num2); 
  17.  
  18.   
  19.  
  20.     MyDelegate myDelegate; 
  21.  
  22.     MyDelegate2 myDelegate2; 
  23.  
  24.     
  25.  
  26.   
  27.  
  28.     void Start () 
  29.  
  30.     { 
  31.  
  32.         //委托類(lèi)型MyDelegate的實(shí)例myDelegate引用的方法 
  33.  
  34.         //是PrintNum 
  35.  
  36.         myDelegate = PrintNum; 
  37.  
  38.         myDelegate(50); 
  39.  
  40.         //委托類(lèi)型MyDelegate2的實(shí)例myDelegate2引用的方法 
  41.  
  42.         //PrintNum的重載版本        
  43.  
  44.         myDelegate2 = PrintNum; 
  45.  
  46.         myDelegate(5050); 
  47.  
  48.     } 
  49.  
  50.     
  51.  
  52.     void PrintNum(int num) 
  53.  
  54.     { 
  55.  
  56.         Debug.Log ("Print Num: " + num); 
  57.  
  58.     } 
  59.  
  60.     
  61.  
  62.     void PrintNum(int num1, int num2) 
  63.  
  64.     { 
  65.  
  66.         int result = num1 + num2; 
  67.  
  68.         Debug.Log ("result num is : " + result); 
  69.  
  70.     } 
  71.  
#p#
這段代碼中有兩個(gè)方法名相同的方法:

void PrintNum(int num)

void PrintNum(int num1, int num2)

那么根據(jù)方法組轉(zhuǎn)換機(jī)制,在向一個(gè)MyDelegate或一個(gè)MyDelegate2賦值時(shí),都可以使用PrintNum作為方法組(此時(shí)有2個(gè)PrintNum,因而是“組”),編譯器會(huì)選擇合適的重載版本。

當(dāng)然,涉及到委托的還有它的另外一個(gè)特點(diǎn)——委托參數(shù)的逆變性和委托返回類(lèi)型的協(xié)變性。這個(gè)特性在很多文章中也有過(guò)介紹,但是這里為了使讀者更加加深印象,因而要具體的介紹一下委托的這種特性。

在為委托實(shí)例引用方法時(shí),C#允許引用類(lèi)型的協(xié)變性和逆變性。協(xié)變性是指方法的返回類(lèi)型可以是從委托的返回類(lèi)型派生的一個(gè)派生類(lèi),也就是說(shuō)協(xié)變性描述的是委托返回類(lèi)型。逆變性則是指方法獲取的參數(shù)的類(lèi)型可以是委托的參數(shù)的類(lèi)型的基類(lèi),換言之逆變性描述的是委托的參數(shù)類(lèi)型。

例如,我們的項(xiàng)目中存在的基礎(chǔ)單位類(lèi)(BaseUnitClass)、士兵類(lèi)(SoldierClass)以及英雄類(lèi)(HeroClass),其中基礎(chǔ)單位類(lèi)BaseUnitClass作為基類(lèi)派生出了士兵類(lèi)SoldierClass和英雄類(lèi)HeroClass,那么我們可以定義一個(gè)委托,就像下面這樣:

  1. delegate Object TellMeYourName(SoldierClass soldier);  

那么我們完全可以通過(guò)構(gòu)造一個(gè)該委托類(lèi)型的實(shí)例來(lái)引用具有以下原型的方法:

  1. string TellMeYourNameMethod(BaseUnitClass base); 

在這個(gè)例子中,TellMeYourNameMethod方法的參數(shù)類(lèi)型是BaseUnitClass,它是TellMeYourName委托的參數(shù)類(lèi)型SoldierClass的基類(lèi),這種參數(shù)的逆變性是允許的;而TellMeYourNameMethod方法的返回值類(lèi)型為string,是派生自TellMeYourName委托的返回值類(lèi)型Object的,因而這種返回類(lèi)型的協(xié)變性也是允許的。但是有一點(diǎn)需要指出的是,協(xié)變性和逆變性?xún)H僅支持引用類(lèi)型,所以如果是值類(lèi)型或void則不支持。下面我們接著舉一個(gè)例子,如果將TellMeYourNameMethod方法的返回類(lèi)型改為值類(lèi)型int,如下:

  1. int TellMeYourNameMethod(BaseUnitClass base); 

這個(gè)方法除了返回類(lèi)型從string(引用類(lèi)型)變成了int(值類(lèi)型)之外,什么都沒(méi)有被改變,但是如果要將這個(gè)方法綁定到剛剛的委托實(shí)例上,編譯器會(huì)報(bào)錯(cuò)。雖然int型和string型一樣,都派生自O(shè)bject類(lèi),但是int型是值類(lèi)型,因而是不支持協(xié)變性的。這一點(diǎn),各位讀者在實(shí)際的開(kāi)發(fā)中一定要注意。

好了,到此我們應(yīng)該對(duì)委托有了一個(gè)初步的直觀印象。在本節(jié)中我?guī)ьI(lǐng)大家直觀的認(rèn)識(shí)了委托如何在代碼中使用,以及通過(guò)C#2引入的方法組轉(zhuǎn)換機(jī)制為委托實(shí)例引用合適的方法以及委托的協(xié)變性和逆變性。那么本節(jié)就到此結(jié)束,接下來(lái)讓我們更進(jìn)一步的探索委托。

0x04 委托是如何實(shí)現(xiàn)的

讓我們重新定義一個(gè)委托并創(chuàng)建它的實(shí)例,之后再為該實(shí)例綁定一個(gè)方法并調(diào)用它:

  1. internal delegate void MyDelegate(int number); 
  2.  
  3. MyDelegate myDelegate = new MyDelegate(myMethod1); 
  4.  
  5. myDelegate = myMethod2; 
  6.  
  7. myDelegate(10); 
從表面看,委托似乎十分簡(jiǎn)單,讓我們拆分一下這段代碼:用C#中的delegate關(guān)鍵字定義了一個(gè)委托類(lèi)型MyDelegate;使用new操作符來(lái)構(gòu)造一個(gè)MyDelegate委托的實(shí)例myDelegate,通過(guò)構(gòu)造函數(shù)創(chuàng)建的委托實(shí)例myDelegate此時(shí)所引用的方法是myMethod1,之后我們通過(guò)方法組轉(zhuǎn)換為myDelegate綁定另一個(gè)對(duì)應(yīng)的方法myMethod2;***,用調(diào)用方法的語(yǔ)法來(lái)調(diào)用回調(diào)函數(shù)??瓷先ヒ磺卸际趾?jiǎn)單,但實(shí)際情況是這樣嗎?

事實(shí)上編譯器和Mono運(yùn)行時(shí)在幕后做了大量的工作來(lái)隱藏委托機(jī)制實(shí)現(xiàn)的復(fù)雜性。那么本節(jié)就要來(lái)揭開(kāi)委托到底是如何實(shí)現(xiàn)的這個(gè)謎題。

下面讓我們把目光重新聚焦在剛剛定義委托類(lèi)型的那行代碼上:

  1. internal delegate void MyDelegate(int number); 

這行對(duì)開(kāi)發(fā)者們來(lái)說(shuō)十分簡(jiǎn)單的代碼背后,編譯器為我們做了哪些幕后的工作呢?

讓我們使用Refactor反編譯C#程序,可以看到如下圖的結(jié)果:

 

可以看到,編譯器實(shí)際上為我們定義了一個(gè)完整的類(lèi)MyDelegate:

  1. internal class MyDelegate : System.MulticastDelegate 
  2.  
  3.  
  4.        //構(gòu)造器 
  5.  
  6.        [MethodImpl(0, MethodCodeType=MethodCodeType.Runtime)] 
  7.  
  8.        public MyDelegate(object @object, IntPtr method); 
  9.  
  10.   
  11.  
  12.        // Invoke這個(gè)方法的原型和源代碼指定的一樣 
  13.  
  14.        [MethodImpl(0, MethodCodeType=MethodCodeType.Runtime)] 
  15.  
  16.        public virtual void Invoke(int number); 
  17.  
  18.   
  19.  
  20.        //以下的兩個(gè)方法實(shí)現(xiàn)對(duì)綁定的回調(diào)函數(shù)的一步回調(diào) 
  21.  
  22.        [MethodImpl(0, MethodCodeType=MethodCodeType.Runtime)] 
  23.  
  24.        public virtual IAsyncResult BeginInvoke(int number, AsyncCallback callback, object @object); 
  25.  
  26.        [MethodImpl(0, MethodCodeType=MethodCodeType.Runtime)] 
  27.  
  28.        public virtual void EndInvoke(IAsyncResult result); 
  29.  
  30.   
  31.  
可以看到,編譯器為我們的MyDelegate類(lèi)定義了4個(gè)方法:一個(gè)構(gòu)造器、Invoke、BeginInvoke以及EndInvoke。而MyDelegate類(lèi)本身又派生自基礎(chǔ)類(lèi)庫(kù)中定義的System.MulticastDelegate類(lèi)型,所以這里需要說(shuō)明的一點(diǎn)是所有的委托類(lèi)型都派生自System.MulticastDelegate。但是各位讀者可能也會(huì)了解到在C#的基礎(chǔ)類(lèi)庫(kù)中還定義了另外一個(gè)委托類(lèi)System.Delegate,甚至System.MulticastDelegate也是從System.Delegate派生而來(lái),而System.Delegate則繼承自System.Object類(lèi)。那么為何會(huì)有兩個(gè)委托類(lèi)呢?這其實(shí)是C#的開(kāi)發(fā)者留下的歷史遺留問(wèn)題,雖然所有我們自己創(chuàng)建的委托類(lèi)型都繼承自MulticastDelegate類(lèi),但是仍然會(huì)有一些Delegate類(lèi)的方法會(huì)被用到。最典型的例子便是Delegate類(lèi)的兩個(gè)靜態(tài)方法Combine和Remove,而這兩個(gè)方法的參數(shù)都是Delegate類(lèi)型的。
  1. public static Delegate Combine( 
  2.  
  3.        Delegate a, 
  4.  
  5.        Delegate b 
  6.  
  7.  
  8.   
  9.  
  10. public static Delegate Remove( 
  11.  
  12.        Delegate source, 
  13.  
  14.        Delegate value 
  15.  
由于我們定義的委托類(lèi)派生自MulticastDelegate而MulticastDelegate又派生自Delegate,因而我們定義的委托類(lèi)型可以作為這兩個(gè)方法的參數(shù)。

再回到我們的MyDelegate委托類(lèi),由于委托是類(lèi),因而凡是能夠定義類(lèi)的地方,都可以定義委托,所以委托類(lèi)既可以在全局范圍中定義,也可以嵌套在一個(gè)類(lèi)型中定義。同樣,委托類(lèi)也有訪問(wèn)修飾符,既可以通過(guò)指定委托類(lèi)的訪問(wèn)修飾符例如:private、internal、public等等來(lái)限定訪問(wèn)權(quán)限。

由于所有的委托類(lèi)型都繼承于MulticastDelegate類(lèi),因而它們也繼承了MulticastDelegate類(lèi)的字段、屬性以及方法,下面列出三個(gè)最重要的非公有字段:

字段

類(lèi)型

作用

_target

System.Object

當(dāng)委托的實(shí)例包裝一個(gè)靜態(tài)方法時(shí),該字段為null;當(dāng)委托的實(shí)例包裝的是一個(gè)實(shí)例方法時(shí),這個(gè)字段引用的是回調(diào)方法要操作的對(duì)象。也就是說(shuō),這個(gè)字段的值是要傳遞給實(shí)例方法的隱式參數(shù)this。

_methodPtr

System.IntPtr

一個(gè)內(nèi)部的整數(shù)值,運(yùn)行時(shí)用該字段來(lái)標(biāo)識(shí)要回調(diào)的方法。

_invocationList

System.Object

該字段的值通常為null。當(dāng)構(gòu)造委托鏈時(shí)它引用一個(gè)委托數(shù)組。

需要注意的一點(diǎn)是,所有的委托都有一個(gè)獲取兩個(gè)參數(shù)的構(gòu)造方法,這兩個(gè)參數(shù)分別是對(duì)對(duì)象的引用以及一個(gè)IntPtr類(lèi)型的用來(lái)引用回調(diào)函數(shù)的句柄(IntPtr 類(lèi)型被設(shè)計(jì)成整數(shù),其大小適用于特定平臺(tái)。 即是說(shuō),此類(lèi)型的實(shí)例在 32 位硬件和操作系統(tǒng)中將是 32 位,在 64 位硬件和操作系統(tǒng)上將是 64 位。IntPtr 對(duì)象常可用于保持句柄。 例如,IntPtr 的實(shí)例廣泛地用在 System.IO.FileStream 類(lèi)中來(lái)保持文件句柄)。代碼如下:

  1. public MyDelegate(object @object, IntPtr method); 

但是我們回去看一看我們構(gòu)造委托類(lèi)型新實(shí)例的代碼:

  1. MyDelegate myDelegate = new MyDelegate(myMethod1); 

似乎和構(gòu)造器的參數(shù)對(duì)不上呀?那為何編譯器沒(méi)有報(bào)錯(cuò),而是讓這段代碼通過(guò)編譯了呢?原來(lái)C#的編譯器知道要?jiǎng)?chuàng)建的是委托的實(shí)例,因而會(huì)分析代碼來(lái)確定引用的是哪個(gè)對(duì)象和哪個(gè)方法。分析之后,將對(duì)象的引用傳遞給object參數(shù),而方法的引用被傳遞給了method參數(shù)。如果myMethod1是靜態(tài)方法,那么object會(huì)傳遞為null。而這個(gè)兩個(gè)方法實(shí)參被傳入構(gòu)造函數(shù)之后,會(huì)分別被_target和_methodPtr這兩個(gè)私有字段保存,并且_ invocationList字段會(huì)被設(shè)為null。

從上面的分析,我們可以得出一個(gè)結(jié)論,即每個(gè)委托對(duì)象實(shí)際上都是一個(gè)包裝了方法和調(diào)用該方法時(shí)要操作的對(duì)象的包裝器。

假設(shè)myMethod1是一個(gè)MyClass類(lèi)定義的實(shí)例方法。那么上面那行創(chuàng)建委托實(shí)例myDelegate的代碼執(zhí)行之后,myDelegate內(nèi)部那三個(gè)字段的值如下:

_target

MyClass的實(shí)例

_methodPtr

myMethod1

_ invocationList

null

假設(shè)myMethod1是一個(gè)MyClass類(lèi)定義的靜態(tài)方法。那么上面那行創(chuàng)建委托實(shí)例myDelegate的代碼執(zhí)行之后,myDelegate內(nèi)部那三個(gè)字段的值如下:

_target

null

_methodPtr

myMethod1

_ invocationList

null

這樣,我們就了解了一個(gè)委托實(shí)例的創(chuàng)建過(guò)程以及其內(nèi)部結(jié)構(gòu)。那么接下來(lái)我們繼續(xù)探索一下,是如何通過(guò)委托實(shí)例來(lái)調(diào)用回調(diào)方法的。首先我們還是通過(guò)一段代碼來(lái)開(kāi)啟我們的討論。

  1. using UnityEngine; 
  2.  
  3. using System.Collections; 
  4.  
  5.   
  6.  
  7.   
  8.  
  9. public class DelegateScript : MonoBehaviour 
  10.  
  11. {   
  12.  
  13.        delegate void MyDelegate(int num); 
  14.  
  15.   
  16.  
  17.     MyDelegate myDelegate; 
  18.  
  19.     
  20.  
  21.   
  22.  
  23.     void Start () 
  24.  
  25.     { 
  26.  
  27.           myDelegate = new MyDelegate(this.PrintNum); 
  28.  
  29.           this.Print(10, myDelegate); 
  30.  
  31.           myDelegate = new MyDelegate(this.PrintDoubleNum); 
  32.  
  33.           this.Print(10, myDelegate); 
  34.  
  35.           myDelegate = null
  36.  
  37.           this.Print(10, myDelegate); 
  38.  
  39.     } 
  40.  
  41.   
  42.  
  43.     void Print(int value, MyDelegate md) 
  44.  
  45.     { 
  46.  
  47.           if(md != null
  48.  
  49.           { 
  50.  
  51.                  md(value); 
  52.  
  53.           } 
  54.  
  55.           else 
  56.  
  57.           { 
  58.  
  59.                  Debug.Log("myDelegate is Null!!!"); 
  60.  
  61.           } 
  62.  
  63.     } 
  64.  
  65.     
  66.  
  67.     void PrintNum(int num) 
  68.  
  69.     { 
  70.  
  71.         Debug.Log ("Print Num: " + num); 
  72.  
  73.     } 
  74.  
  75.     
  76.  
  77.     void PrintDoubleNum(int num) 
  78.  
  79.     { 
  80.  
  81.         int result = num + num; 
  82.  
  83.         Debug.Log ("result num is : " + result); 
  84.  
  85.     } 
  86.  

編譯并且運(yùn)行之后,輸出的結(jié)果如下:

  1. Print Num:10 
  2.  
  3. result num is : 20 
  4.  
  5. myDelegate is Null!!! 

#p#

我們可以注意到,我們新定義的Print方法將委托實(shí)例作為了其中的一個(gè)參數(shù)。并且首先檢查傳入的委托實(shí)例md是否為null。那么這一步是否是多此一舉的操作呢?答案是否定的,檢查md是否為null是必不可少的,這是由于md僅僅是可能引用了MyDelegate類(lèi)的實(shí)例,但它也有可能是null,就像代碼中的第三種情況所演示的那樣。經(jīng)過(guò)檢查,如果md不是null,則調(diào)用回調(diào)方法,不過(guò)代碼看上去似乎是調(diào)用了一個(gè)名為md,參數(shù)為value的方法:md(value);但事實(shí)上并沒(méi)有一個(gè)叫做md的方法存在,那么編譯器是如何來(lái)調(diào)用正確的回調(diào)方法的呢?原來(lái)編譯器知道m(xù)d是引用了委托實(shí)例的變量,因而在幕后會(huì)生成代碼來(lái)調(diào)用該委托實(shí)例的Invoke方法。換言之,上面剛剛調(diào)用回調(diào)函數(shù)的代碼md(value);被編譯成了如下的形式:

  1. md.Invoke(value); 

為了更深一步的觀察編譯器的行為,我們將編譯后的代碼反編譯為CIL代碼。并且截取其中Print方法部分的CIL代碼:

  1. // method line 4 
  2.  
  3. .method private hidebysig 
  4.  
  5.        instance default void Print (int32 'value'class DelegateScript/MyDelegate md)  cil managed 
  6.  
  7.  
  8.     // Method begins at RVA 0x20c8 
  9.  
  10. // Code size 29 (0x1d) 
  11.  
  12. .maxstack 8 
  13.  
  14. IL_0000:  ldarg.2 
  15.  
  16. IL_0001:  brfalse IL_0012 
  17.  
  18.   
  19.  
  20. IL_0006:  ldarg.2 
  21.  
  22. IL_0007:  ldarg.1 
  23.  
  24. IL_0008:  callvirt instance void class DelegateScript/MyDelegate::Invoke(int32) 
  25.  
  26. IL_000d:  br IL_001c 
  27.  
  28.   
  29.  
  30. IL_0012:  ldstr "myDelegate is Null!!!" 
  31.  
  32. IL_0017:  call void class [mscorlib]System.Console::WriteLine(string) 
  33.  
  34. IL_001c:  ret 
  35.  
  36. // end of method DelegateScript::Print 

分析這段代碼,我們可以發(fā)現(xiàn)在IL_0008這行,編譯器為我們調(diào)用了DelegateScript/MyDelegate::Invoke(int32)方法。那么我們是否可以顯式的調(diào)用md的Invoke方法呢?答案是Yes。所以,Print方法完全可以改成如下的定義:

  1. void Print(int value, MyDelegate md) 
  2.  
  3.     { 
  4.  
  5.           if(md != null
  6.  
  7.           { 
  8.  
  9.                  md.Invoke(value); 
  10.  
  11.           } 
  12.  
  13.           else 
  14.  
  15.           { 
  16.  
  17.                  Debug.Log("myDelegate is Null!!!"); 
  18.  
  19.           } 
  20.  
  21.     } 

而一旦調(diào)用了委托實(shí)例的Invoke方法,那么之前在構(gòu)造委托實(shí)例時(shí)被賦值的字段_target和_methodPtr在此時(shí)便派上了用場(chǎng),它們會(huì)為Invoke方法提供對(duì)象和方法信息,使得Invoke能夠在指定的對(duì)象上調(diào)用包裝好的回調(diào)方法。OK,本節(jié)討論了編譯器如何在幕后為我們生成委托類(lèi)、委托實(shí)例的內(nèi)部結(jié)構(gòu)以及如何利用委托實(shí)例的Invoke方法來(lái)調(diào)用一個(gè)回調(diào)函數(shù),那么我們接下來(lái)繼續(xù)來(lái)討論一下如何使用委托來(lái)回調(diào)多個(gè)方法。

0x05 委托是如何調(diào)用多個(gè)方法的?

為了方便,我們將用委托調(diào)用多個(gè)方法簡(jiǎn)稱(chēng)為委托鏈。而委托鏈?zhǔn)俏袑?duì)象的集合,可以利用委托鏈來(lái)調(diào)用集合中的委托所代表的全部方法。為了使各位能夠更加直觀的了解委托鏈,下面我們通過(guò)一段代碼來(lái)作為演示:

  1. using UnityEngine; 
  2.  
  3. using System; 
  4.  
  5. using System.Collections; 
  6.  
  7.   
  8.  
  9.   
  10.  
  11. public class DelegateScript : MonoBehaviour 
  12.  
  13. {   
  14.  
  15.        delegate void MyDelegate(int num); 
  16.  
  17.        
  18.  
  19.        void Start () 
  20.  
  21.        { 
  22.  
  23.               //創(chuàng)建3個(gè)MyDelegate委托類(lèi)的實(shí)例 
  24.  
  25.               MyDelegate myDelegate1 = new MyDelegate(this.PrintNum); 
  26.  
  27.               MyDelegate myDelegate2 = new MyDelegate(this.PrintDoubleNum); 
  28.  
  29.               MyDelegate myDelegate3 = new MyDelegate(this.PrintTripleNum); 
  30.  
  31.   
  32.  
  33.               MyDelegate myDelegates = null
  34.  
  35.               //使用Delegate類(lèi)的靜態(tài)方法Combine 
  36.  
  37.               myDelegates = (MyDelegate)Delegate.Combine(myDelegates, myDelegate1); 
  38.  
  39.               myDelegates = (MyDelegate)Delegate.Combine(myDelegates, myDelegate2); 
  40.  
  41.               myDelegates = (MyDelegate)Delegate.Combine(myDelegates, myDelegate3); 
  42.  
  43.               //將myDelegates傳入Print方法 
  44.  
  45.               this.Print(10, myDelegates); 
  46.  
  47.        } 
  48.  
  49.        
  50.  
  51.        void Print(int value, MyDelegate md) 
  52.  
  53.        { 
  54.  
  55.               if(md != null
  56.  
  57.               { 
  58.  
  59.                      md(value); 
  60.  
  61.               } 
  62.  
  63.               else 
  64.  
  65.               { 
  66.  
  67.                      Debug.Log("myDelegate is Null!!!"); 
  68.  
  69.               } 
  70.  
  71.        } 
  72.  
  73.        
  74.  
  75.        void PrintNum(int num) 
  76.  
  77.        { 
  78.  
  79.               Debug.Log ("1 result Num: " + num); 
  80.  
  81.        } 
  82.  
  83.        
  84.  
  85.        void PrintDoubleNum(int num) 
  86.  
  87.        { 
  88.  
  89.               int result = num + num; 
  90.  
  91.               Debug.Log ("2 result num is : " + result); 
  92.  
  93.        } 
  94.  
  95.        void PrintTripleNum(int num) 
  96.  
  97.        { 
  98.  
  99.               int result = num + num + num; 
  100.  
  101.               Debug.Log ("3 result num is : " + result); 
  102.  
  103.        } 
  104.  
  105.   
  106.  

編譯并且運(yùn)行之后(將該腳本掛載在某個(gè)游戲物體上,運(yùn)行Unity3D即可),可以看到Unity3D的調(diào)試窗口打印出了如下內(nèi)容:

  1. 1 result Num: 10 
  2.  
  3. 2 result Num: 20 
  4.  
  5. 3 result Num: 30 

換句話說(shuō),一個(gè)委托實(shí)例myDelegates中調(diào)用了三個(gè)回調(diào)方法PrintNum、PrintDoubleNum以及PrintTripleNum。下面,讓我們來(lái)分析一下這段代碼。我們首先構(gòu)造了三個(gè)MyDelegate委托類(lèi)的實(shí)例,并分別賦值給myDelegate1、myDelegate2、myDelegate3這三個(gè)變量。而之后的myDelegates初始化為null,即表明了此時(shí)沒(méi)有要回調(diào)的方法,之后我們要用它來(lái)引用委托鏈,或者說(shuō)是引用一些委托實(shí)例的集合,而這些實(shí)例中包裝了要被回調(diào)的回調(diào)方法。那么應(yīng)該如何將委托實(shí)例加入到委托鏈中呢?不錯(cuò),前文提到過(guò)基礎(chǔ)類(lèi)庫(kù)中的另一個(gè)委托類(lèi)Delegate,它有一個(gè)公共靜態(tài)方法Combine是專(zhuān)門(mén)來(lái)處理這種需求的,所以接下來(lái)我們就調(diào)用了Delegate.Combine方法將委托加入到委托鏈中。

  1. myDelegates = (MyDelegate)Delegate.Combine(myDelegates, myDelegate1); 
  2.  
  3. myDelegates = (MyDelegate)Delegate.Combine(myDelegates, myDelegate2); 
  4.  
  5. myDelegates = (MyDelegate)Delegate.Combine(myDelegates, myDelegate3);  

在***行代碼中,由于此時(shí)myDelegates是null,因而當(dāng)Delegate.Combine方法發(fā)現(xiàn)要合并的是null和一個(gè)委托實(shí)例myDelegate1時(shí),Delegate.Combine會(huì)直接返回myDelegate1的值,因而***行代碼執(zhí)行完畢之后,myDelegates現(xiàn)在引用了myDelegate1所引用的委托實(shí)例。

當(dāng)?shù)诙握{(diào)用Delegate.Combine方法,繼續(xù)合并myDelegates和myDelegate2的時(shí)候,Delegate.Combine方法檢測(cè)到myDelegates已經(jīng)不再是null而是引用了一個(gè)委托實(shí)例,此時(shí)Delegate.Combine方法會(huì)構(gòu)建一個(gè)不同于myDelegates和myDelegate2的新的委托實(shí)例。這個(gè)新的委托實(shí)例自然會(huì)對(duì)上文常常提起的_target和_methodPtr這兩個(gè)私有字段進(jìn)行初始化,但是此時(shí)需要注意的是,之前一直沒(méi)有實(shí)際值的_invocationList字段此時(shí)被初始化為一個(gè)對(duì)委托實(shí)例數(shù)組的引用。該數(shù)組的***個(gè)元素便是包裝了***個(gè)委托實(shí)例myDelegate1所引用的PrintNum方法的一個(gè)委托實(shí)例(即myDelegates此時(shí)所引用的委托實(shí)例),而數(shù)組的第二個(gè)元素則是包裝了第二個(gè)委托實(shí)例myDelegate2所引用的PrintDoubleNum方法的委托實(shí)例(即myDelegate2所引用的委托實(shí)例)。之后,將這個(gè)新創(chuàng)建的委托實(shí)例的引用賦值給myDelegates變量,此時(shí)myDelegates指向了這個(gè)包裝了兩個(gè)回調(diào)方法的新的委托實(shí)例。

接下來(lái),我們第三次調(diào)用了Delegate.Combine方法,繼續(xù)將委托實(shí)例合并到一個(gè)委托鏈中。這次編譯器內(nèi)部發(fā)生的事情和上一次大同小異,Delegate.Combine方法檢測(cè)到myDelegates已經(jīng)引用了一個(gè)委托實(shí)例,同樣地,這次仍然會(huì)創(chuàng)建一個(gè)新的委托實(shí)例,新委托實(shí)例中的那兩個(gè)私有字段_target和_methodPtr同樣會(huì)被初始化,而_invocationList字段此時(shí)同樣被初始化為一個(gè)對(duì)委托實(shí)例數(shù)組的引用,只不過(guò)這次的元素多了一個(gè)包裝了第三個(gè)委托實(shí)例myDelegate3中所引用的PrintDoubleNum方法的委托實(shí)例(即myDelegate3所引用的委托實(shí)例)。之后,將這個(gè)新創(chuàng)建的委托實(shí)例的引用賦值給myDelegates變量,此時(shí)myDelegates指向了這個(gè)包裝了三個(gè)回調(diào)方法的新的委托實(shí)例。而上一次合并中_invocationList字段所引用的委托實(shí)例數(shù)組,此時(shí)不再需要,因而可以被垃圾回收。

當(dāng)所有的委托實(shí)例都合并到一個(gè)委托鏈中,并且myDelegates變量引用了該委托鏈之后,我們將myDelegates變量作為參數(shù)傳入Print方法中,正如前文所述,此時(shí)Print方法中的代碼會(huì)隱式的調(diào)用MyDelegate委托類(lèi)型的實(shí)例的Invoke方法,也就是調(diào)用myDelegates變量所引用的委托實(shí)例的Invoke方法。此時(shí)Invoke方法發(fā)現(xiàn)_invocationList字段已經(jīng)不再是null而是引用了一個(gè)委托實(shí)例的數(shù)組,因此會(huì)執(zhí)行一個(gè)循環(huán)來(lái)遍歷該數(shù)組中的所有元素,并按照順序調(diào)用每個(gè)元素(委托實(shí)例)中包裝的回調(diào)方法。所以,PrintNum方法首先會(huì)被調(diào)用,緊跟著的是PrintDoubleNum方法,***則是PrintTripleNum方法。

有合并,對(duì)應(yīng)的自然就有拆解。因而Delegate除了提供了Combine方法用來(lái)合并委托實(shí)例之外,還提供了Remove方法用來(lái)移除委托實(shí)例。例如我們想移除包裝了PrintDoubleNum方法的委托實(shí)例,那么使用Delegate.Remove的代碼如下:

  1. myDelegates = (MyDelegate)Delegate.Remove(myDelegates, new MyDelegate(PrintDoubleNum)); 

當(dāng)Delegate.Remove方法被調(diào)用時(shí),它會(huì)從后向前掃描myDelegates所引用的委托實(shí)例中的委托數(shù)組,并且對(duì)比委托數(shù)組中的元素的_target字段和_methodPtr字段的值是否與第二個(gè)參數(shù)即新建的MyDelegate委托類(lèi)的實(shí)例中的_target字段和_methodPtr字段的值匹配。如果匹配,且刪除該元素之后,委托實(shí)例數(shù)組中只剩余一個(gè)元素,則直接返回該元素(委托實(shí)例);如果刪除該元素之后,委托實(shí)例數(shù)組中還有多個(gè)元素,那么就會(huì)創(chuàng)建一個(gè)新的委托實(shí)例,這個(gè)新創(chuàng)建的委托實(shí)例的_invocationList字段會(huì)引用一個(gè)由刪除了目標(biāo)元素之后剩余的元素所組成的委托實(shí)例數(shù)組,之后返回該委托實(shí)例的引用。當(dāng)然,如果刪除匹配實(shí)例之后,委托實(shí)例數(shù)組變?yōu)榭?,那么Remove就會(huì)返回null。需要注意的一點(diǎn)是,Remove方法每次僅僅移除一個(gè)匹配的委托實(shí)例,而不是刪除所有和目標(biāo)委托實(shí)例匹配的委托實(shí)例。

當(dāng)然,如果每次合并委托和刪除委托都要寫(xiě)Delegate.Combine和Delegate. Remove則未免顯得太過(guò)繁瑣,所以為了方便使用C#語(yǔ)言的開(kāi)發(fā)者,C#編譯器為委托類(lèi)型的實(shí)例重載了+=和-+操作符來(lái)對(duì)應(yīng)Delegate.Combine和Delegate. Remove。具體的例子,我們可以看看下面的這段代碼。

  1. using UnityEngine; 
  2.  
  3. using System.Collections; 
  4.  
  5.   
  6.  
  7. public class MulticastScript : MonoBehaviour 
  8.  
  9.  
  10.     delegate void MultiDelegate(); 
  11.  
  12.     MultiDelegate myMultiDelegate; 
  13.  
  14.      
  15.  
  16.   
  17.  
  18.     void Start () 
  19.  
  20.     { 
  21.  
  22.         myMultiDelegate += PowerUp; 
  23.  
  24.         myMultiDelegate += TurnRed; 
  25.  
  26.         
  27.  
  28.         if(myMultiDelegate != null
  29.  
  30.         { 
  31.  
  32.             myMultiDelegate(); 
  33.  
  34.         } 
  35.  
  36.     } 
  37.  
  38.     
  39.  
  40.     void PowerUp() 
  41.  
  42.     { 
  43.  
  44.         print ("Orb is powering up!"); 
  45.  
  46.     } 
  47.  
  48.     
  49.  
  50.     void TurnRed() 
  51.  
  52.     { 
  53.  
  54.         renderer.material.color = Color.red; 
  55.  
  56.     } 
  57.  

好,我想到此我已經(jīng)回答了本小節(jié)題目中所提出的那個(gè)問(wèn)題:委托是如何調(diào)用多個(gè)方法的。但是為了要實(shí)現(xiàn)觀察者模式甚至是我們自己的消息系統(tǒng),還有一個(gè)大人物不得不介紹,那就是和委托關(guān)系密切的事件,那么下一篇博客就讓我們走進(jìn)委托和事件的世界中吧。

 

責(zé)任編輯:倪明 來(lái)源: 博客園
相關(guān)推薦

2013-03-19 14:44:25

U3D手游手機(jī)游戲引擎

2025-06-23 08:25:00

SFINAEC++編譯器

2020-10-26 11:33:45

編程語(yǔ)言編譯器軟件

2022-05-18 09:31:42

編譯器開(kāi)源代碼生成

2010-03-23 11:17:16

Python 動(dòng)態(tài)編譯

2010-10-20 13:43:37

C++編譯器

2010-01-12 16:42:59

C++編譯器

2017-08-29 09:30:01

編譯器LLVM編程語(yǔ)言

2009-02-24 08:48:02

D語(yǔ)言C++編譯器

2015-09-20 21:21:20

2010-01-14 16:46:13

CentOS Mysq

2016-11-08 18:53:08

編譯器

2010-01-21 09:11:38

C++編譯器

2020-01-10 18:04:01

Python編程語(yǔ)言Windows

2010-01-18 10:34:21

C++編譯器

2022-12-28 08:52:15

編譯器自動(dòng)內(nèi)存管理

2009-08-10 17:12:54

C#編譯器

2017-03-20 18:01:55

編譯器匯編

2013-03-29 10:02:37

編譯器語(yǔ)言編譯開(kāi)發(fā)

2020-12-07 09:20:59

編譯器工具代碼
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)