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

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

匹夫細說C#:庖丁解牛聊委托,那些編譯器藏的和U3D給的

2019-11-14 14:10:44
字體:
來源:轉載
供稿:網友

0x00 前言

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

0x01 從觀察者模式說起

在設計模式中,有一種我們常常會用到的設計模式——觀察者模式。那么這種設計模式和我們的主題“如何在Unity3D中使用委托”有什么關系呢?別急,先讓我們來聊一聊什么是觀察者模式。

首先讓我們來看看報紙和雜志的訂閱是怎么一回事:

  1. 報社的任務便是出版報紙。
  2. 向某家報社訂閱他們的報紙,只要他們有新的報紙出版便會向你發放。也就是說,只要你是他們的訂閱客戶,便可以一直收到新的報紙。
  3. 如果不再需要這份報紙,則可以取消訂閱。取消之后,報社便不會再送新的報紙過來。
  4. 報社和訂閱者是兩個不同的主體,只要報社還一直存在著,不同的訂閱者便可以來訂閱或取消訂閱。

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

當我們試圖去勾勒觀察者模式時,可以使用報紙訂閱服務,或者出版者和訂閱者來比擬。而在實際的開發中,觀察者模式被定義為了如下這樣:

觀察者模式:定義了對象之間的一對多依賴,這樣一來,當一個對象改變狀態時,它的所有依賴者都會收到通知并自動更新。

那么介紹了這么多觀察者模式,是不是也該說一說委托了呢?是的,C#語言通過委托來實現回調函數的機制,而回調函數是一種很有用的編程機制,可以被廣泛的用在觀察者模式中。

那么Unity3D本身是否有提供這種機制呢?答案也是肯定的,那么和委托又有什么區別呢?下面就讓我們來聊一聊這個話題。

0x02 向Unity3D中的SendMessage和BroadcastMessage說拜拜

當然,不可否認Unity3D游戲引擎的出現是游戲開發者的一大福音。但不得不說的是,Unity3D的游戲腳本的架構中是存在一些缺陷的。一個很好的例子就是本節要說的圍繞SendMessage和BroadcastMessage而構建的消息系統。之所以說Unity3D的這套消息系統存在缺陷,主要是由于SendMessage和BroadcastMessage過于依賴反射機制(reflection)來查找消息對應的回調函數。頻繁的使用反射自然會影響性能,但是性能的損耗還并非最為嚴重的問題,更加嚴重的問題是使用這種機制之后代碼的維護成本。為什么說這樣做是一個很糟糕的事情呢?因為使用字符串來標識一個方法可能會導致很多隱患的出現。舉一個例子:假如開發團隊中某個開發者決定要重構某些代碼,很不巧,這部分代碼便是那些可能要被這些消息調用的方法定義的代碼,那么如果方法被重新命名甚至被刪除,是否會導致很嚴重的隱患呢?答案是yes。這種隱患的可怕之處并不在于可能引發的編譯時錯誤,恰恰相反,這種隱患的可怕之處在于編譯器可能都不會報錯來提醒開發者某些方法已經被改名甚至是不存在了,面對一個能夠正常的運行程序而沒有警覺是最可怕的,而什么時候這個隱患會爆發呢?就是觸發了特定的消息而找不到對應的方法的時候 ,但這時候發現問題所在往往已經太遲了。

另一個潛在的問題是由于使用了反射機制因而Unity3D的這套消息系統也能夠調用聲明為私有的方法的。但是如果一個私有方法在聲明的類的內部沒有被使用,那么正常的想法肯定都認為這是一段廢代碼,因為在這個類的外部不可能有人會調用它。那么對待廢代碼的態度是什么呢?我想很多開發者都會選擇消滅這段廢代碼,那么同樣的隱患又會出現,可能在編譯時并沒有問題,甚至程序也能正常運行一段時間,但是只要觸發了特定的消息而沒有對應的方法,那便是這種隱患爆發的時候。因而,是時候向Unity3D中的SendMessage和BroadcastMessage說拜拜了,讓我們選擇C#的委托來實現自己的消息機制吧。

0x03 認識回調函數機制----委托

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

using UnityEngine;using System.Collections;  public class DelegateScript : MonoBehaviour{      //聲明一個委托類型,它的實例引用一個方法    internal delegate void MyDelegate(int num);    MyDelegate myDelegate;        void Start ()    {        //委托類型MyDelegate的實例myDelegate引用的方法        //PRintNum        myDelegate = PrintNum;        myDelegate(50);        //委托類型MyDelegate的實例myDelegate引用的方法        //DoubleNum               myDelegate = DoubleNum;        myDelegate(50);    }       void PrintNum(int num)    {        Debug.Log ("Print Num: " + num);    }       void DoubleNum(int num)    {        Debug.Log ("Double Num: " + num * 2);    }}

下面我們來看看這段代碼做的事情。在最開始,我們可以看到internal委托類型MyDelegate的聲明。委托要確定一個回調方法簽名,包括參數以及返回類型等等,在本例中MyDelegate委托制定的回調方法的參數類型是int型,同時返回類型為void。

DelegateScript類還定義了兩個私有方法PrintNum和DoubleNum,它們的分別實現了打印傳入的參數和打印傳入的參數的兩倍的功能。在Start方法中,MyDelegate類的實例myDelegate分別引用了這兩個方法,并且分別調用了這兩個方法。

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

new MyDelegate(PrintNum);

即便回到C#1的時代,這行創建新的委托實例的代碼看上去似乎并沒有讓開發者產生什么不好的印象,但是如果是作為較長的一個表達式的一部分時,就會讓人感覺很冗繁了。一個明顯的例子是在啟動一個新的線程時候的表達式:

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

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

//使用方法組轉換時,隱式轉換會將//一個方法組轉換為具有兼容簽名的//任意委托類型myDelegate = PrintNum;Thread th = new Thread(Method);

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

using UnityEngine;using System.Collections;public class DelegateScript : MonoBehaviour{      //聲明一個委托類型,它的實例引用一個方法delegate void MyDelegate(int num);    //聲明一個委托類型,它的實例引用一個方法    delegate void MyDelegate2(int num, int num2);     MyDelegate myDelegate;    MyDelegate2 myDelegate2;        void Start ()    {        //委托類型MyDelegate的實例myDelegate引用的方法        //是PrintNum        myDelegate = PrintNum;        myDelegate(50);        //委托類型MyDelegate2的實例myDelegate2引用的方法        //PrintNum的重載版本               myDelegate2 = PrintNum;        myDelegate(50, 50);    }       void PrintNum(int num)    {        Debug.Log ("Print Num: " + num);    }       void PrintNum(int num1, int num2)    {        int result = num1 + num2;        Debug.Log ("result num is : " + result);    }}

這段代碼中有兩個方法名相同的方法:

void PrintNum(int num)

void PrintNum(int num1, int num2)

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

當然,涉及到委托的還有它的另外一個特點——委托參數的逆變性和委托返回類型的協變性。這個特性在很多文章中也有過介紹,但是這里為了使讀者更加加深印象,因而要具體的介紹一下委托的這種特性。

在為委托實例引用方法時,C#允許引用類型的協變性和逆變性。協變性是指方法的返回類型可以是從委托的返回類型派生的一個派生類,也就是說協變性描述的是委托返回類型。逆變性則是指方法獲取的參數的類型可以是委托的參數的類型的基類,換言之逆變性描述的是委托的參數類型。

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

delegate Object TellMeYourName(SoldierClass soldier);

那么我們完全可以通過構造一個該委托類型的實例來引用具有以下原型的方法:

string TellMeYourNameMethod(BaseUnitClass base);

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

int TellMeYourNameMethod(BaseUnitClass base);

這個方法除了返回類型從string(引用類型)變成了int(值類型)之外,什么都沒有被改變,但是如果要將這個方法綁定到剛剛的委托實例上,編譯器會報錯。雖然int型和string型一樣,都派生自Object類,但是int型是值類型,因而是不支持協變性的。這一點,各位讀者在實際的開發中一定要注意。

好了,到此我們應該對委托有了一個初步的直觀印象。在本節中我帶領大家直觀的認識了委托如何在代碼中使用,以及通過C#2引入的方法組轉換機制為委托實例引用合適的方法以及委托的協變性和逆變性。那么本節就到此結束,接下來讓我們更進一步的探索委托。

0x04 委托是如何實現的

讓我們重新定義一個委托并創建它的實例,之后再為該實例綁定一個方法并調用它:

internal delegate void MyDelegate(int number);MyDelegate myDelegate = new MyDelegate(myMethod1);myDelegate = myMethod2;myDelegate(10);

從表面看,委托似乎十分簡單,讓我們拆分一下這段代碼:用C#中的delegate關鍵字定義了一個委托類型MyDelegate;使用new操作符來構造一個MyDelegate委托的實例myDelegate,通過構造函數創建的委托實例myDelegate此時所引用的方法是myMethod1,之后我們通過方法組轉換為myDelegate綁定另一個對應的方法myMethod2;最后,用調用方法的語法來調用回調函數??瓷先ヒ磺卸际趾唵危珜嶋H情況是這樣嗎?

事實上編譯器和Mono運行時在幕后做了大量的工作來隱藏委托機制實現的復雜性。那么本節就要來揭開委托到底是如何實現的這個謎題。

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

internal delegate void MyDelegate(int number);

這行對開發者們來說十分簡單的代碼背后,編譯器為我們做了哪些幕后的工作呢?

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

 

可以看到,編譯器實際上為我們定義了一個完整的類MyDelegate:

internal class MyDelegate : System.MulticastDelegate{       //構造器       [MethodImpl(0, MethodCodeType=MethodCodeType.Runtime)]       public MyDelegate(object @object, IntPtr method);        // Invoke這個方法的原型和源代碼指定的一樣       [MethodImpl(0, MethodCodeType=MethodCodeType.Runtime)]       public virtual void Invoke(int number);        //以下的兩個方法實現對綁定的回調函數的一步回調       [MethodImpl(0, MethodCodeType=MethodCodeType.Runtime)]       public virtual IAsyncResult BeginInvoke(int number, AsyncCallback callback, object @object);       [MethodImpl(0, MethodCodeType=MethodCodeType.Runtime)]       public virtual void EndInvoke(IAsyncResult result); }

可以看到,編譯器為我們的MyDelegate類定義了4個方法:一個構造器、Invoke、BeginInvoke以及EndInvoke。而MyDelegate類本身又派生自基礎類庫中定義的System.MulticastDelegate類型,所以這里需要說明的一點是所有的委托類型都派生自System.MulticastDelegate。但是各位讀者可能也會了解到在C#的基礎類庫中還定義了另外一個委托類System.Delegate,甚至System.MulticastDelegate也是從System.Delegate派生而來,而System.Delegate則繼承自System.Object類。那么為何會有兩個委托類呢?這其實是C#的開發者留下的歷史遺留問題,雖然所有我們自己創建的委托類型都繼承自MulticastDelegate類,但是仍然會有一些Delegate類的方法會被用到。最典型的例子便是Delegate類的兩個靜態方法Combine和Remove,而這兩個方法的參數都是Delegate類型的。

public static Delegate Combine(       Delegate a,       Delegate b) public static Delegate Remove(       Delegate source,       Delegate value)

由于我們定義的委托類派生自MulticastDelegate而MulticastDelegate又派生自Delegate,因而我們定義的委托類型可以作為這兩個方法的參數。

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

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

字段

類型

作用

_target

System.Object

當委托的實例包裝一個靜態方法時,該字段為null;當委托的實例包裝的是一個實例方法時,這個字段引用的是回調方法要操作的對象。也就是說,這個字段的值是要傳遞給實例方法的隱式參數this。

_methodPtr

System.IntPtr

一個內部的整數值,運行時用該字段來標識要回調的方法。

_invocationList

System.Object

該字段的值通常為null。當構造委托鏈時它引用一個委托數組。

需要注意的一點是,所有的委托都有一個獲取兩個參數的構造方法,這兩個參數分別是對對象的引用以及一個IntPtr類型的用來引用回調函數的句柄(IntPtr 類型被設計成整數,其大小適用于特定平臺。 即是說,此類型的實例在 32 位硬件和操作系統中將是 32 位,在 64 位硬件和操作系統上將是 64 位。IntPtr 對象??捎糜诒3志浔?例如,IntPtr 的實例廣泛地用在 System.IO.FileStream 類中來保持文件句柄)。代碼如下:

public MyDelegate(object @object, IntPtr method);

但是我們回去看一看我們構造委托類型新實例的代碼:

MyDelegate myDelegate = new MyDelegate(myMethod1);

似乎和構造器的參數對不上呀?那為何編譯器沒有報錯,而是讓這段代碼通過編譯了呢?原來C#的編譯器知道要創建的是委托的實例,因而會分析代碼來確定引用的是哪個對象和哪個方法。分析之后,將對象的引用傳遞給object參數,而方法的引用被傳遞給了method參數。如果myMethod1是靜態方法,那么object會傳遞為null。而這個兩個方法實參被傳入構造函數之后,會分別被_target和_methodPtr這兩個私有字段保存,并且_ invocationList字段會被設為null。

從上面的分析,我們可以得出一個結論,即每個委托對象實際上都是一個包裝了方法和調用該方法時要操作的對象的包裝器。

假設myMethod1是一個MyClass類定義的實例方法。那么上面那行創建委托實例myDelegate的代碼執行之后,myDelegate內部那三個字段的值如下:

_target

MyClass的實例

_methodPtr

myMethod1

_ invocationList

null

假設myMethod1是一個MyClass類定義的靜態方法。那么上面那行創建委托實例myDelegate的代碼執行之后,myDelegate內部那三個字段的值如下:

_target

null

_methodPtr

myMethod1

_ invocationList

null

這樣,我們就了解了一個委托實例的創建過程以及其內部結構。那么接下來我們繼續探索一下,是如何通過委托實例來調用回調方法的。首先我們還是通過一段代碼來開啟我們的討論。

using UnityEngine;using System.Collections;  public class DelegateScript : MonoBehaviour{         delegate void MyDelegate(int num);     MyDelegate myDelegate;        void Start ()    {          myDelegate = new MyDelegate(this.PrintNum);          this.Print(10, myDelegate);          myDelegate = new MyDelegate(this.PrintDoubleNum);          this.Print(10, myDelegate);          myDelegate = null;          this.Print(10, myDelegate);    }     void Print(int value, MyDelegate md)    {          if(md != null)          {                 md(value);          }          else          {                 Debug.Log("myDelegate is Null!!!");          }    }       void PrintNum(int num)    {        Debug.Log ("Print Num: " + num);    }       void PrintDoubleNum(int num)    {        int result = num + num;        Debug.Log ("result num is : " + result);    }}

編譯并且運行之后,輸出的結果如下:

Print Num:10result num is : 20myDelegate is Null!!!

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

md.Invoke(value);

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

// method line 4.method private hidebysig       instance default void Print (int32 'value', class DelegateScript/MyDelegate md)  cil managed{    // Method begins at RVA 0x20c8// Code size 29 (0x1d).maxstack 8IL_0000:  ldarg.2IL_0001:  brfalse IL_0012 IL_0006:  ldarg.2IL_0007:  ldarg.1IL_0008:  callvirt instance void class DelegateScript/MyDelegate::Invoke(int32)IL_000d:  br IL_001c IL_0012:  ldstr "myDelegate is Null!!!"IL_0017:  call void class [mscorlib]System.Console::WriteLine(string)IL_001c:  ret} // end of method DelegateScript::Print

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

    void Print(int value, MyDelegate md)    {          if(md != null)          {                 md.Invoke(value);          }          else          {                 Debug.Log("myDelegate is Null!!!");          }    }

而一旦調用了委托實例的Invoke方法,那么之前在構造委托實例時被賦值的字段_target和_methodPtr在此時便派上了用場,它們會為Invoke方法提供對象和方法信息,使得Invoke能夠在指定的對象上調用包裝好的回調方法。OK,本節討論了編譯器如何在幕后為我們生成委托類、委托實例的內部結構以及如何利用委托實例的Invoke方法來調用一個回調函數,那么我們接下來繼續來討論一下如何使用委托來回調多個方法。

0x05 委托是如何調用多個方法的?

為了方便,我們將用委托調用多個方法簡稱為委托鏈。而委托鏈是委托對象的集合,可以利用委托鏈來調用集合中的委托所代表的全部方法。為了使各位能夠更加直觀的了解委托鏈,下面我們通過一段代碼來作為演示:

using UnityEngine;using System;using System.Collections;  public class DelegateScript : MonoBehaviour{         delegate void MyDelegate(int num);             void Start ()       {              //創建3個MyDelegate委托類的實例              MyDelegate myDelegate1 = new MyDelegate(this.PrintNum);              MyDelegate myDelegate2 = new MyDelegate(this.PrintDoubleNum);              MyDelegate myDelegate3 = new MyDelegate(this.PrintTripleNum);               MyDelegate myDelegates = null;              //使用Delegate類的靜態方法Combine              myDelegates = (MyDelegate)Delegate.Combine(myDelegates, myDelegate1);              myDelegates = (MyDelegate)Delegate.Combine(myDelegates, myDelegate2);              myDelegates = (MyDelegate)Delegate.Combine(myDelegates, myDelegate3);              //將myDelegates傳入Print方法              this.Print(10, myDelegates);       }             void Print(int value, MyDelegate md)       {              if(md != null)              {                     md(value);              }              else              {                     Debug.Log("myDelegate is Null!!!");              }       }             void PrintNum(int num)       {              Debug.Log ("1 result Num: " + num);       }             void PrintDoubleNum(int num)       {              int result = num + num;              Debug.Log ("2 result num is : " + result);       }       void PrintTripleNum(int num)       {              int result = num + num + num;              Debug.Log ("3 result num is : " + result);       } }

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

1 result Num: 102 result Num: 203 result Num: 30

換句話說,一個委托實例myDelegates中調用了三個回調方法PrintNum、PrintDoubleNum以及PrintTripleNum。下面,讓我們來分析一下這段代碼。我們首先構造了三個MyDelegate委托類的實例,并分別賦值給myDelegate1、myDelegate2、myDelegate3這三個變量。而之后的myDelegates初始化為null,即表明了此時沒有要回調的方法,之后我們要用它來引用委托鏈,或者說是引用一些委托實例的集合,而這些實例中包裝了要被回調的回調方法。那么應該如何將委托實例加入到委托鏈中呢?不錯,前文提到過基礎類庫中的另一個委托類Delegate,它有一個公共靜態方法Combine是專門來處理這種需求的,所以接下來我們就調用了Delegate.Combine方法將委托加入到委托鏈中。

              myDelegates = (MyDelegate)Delegate.Combine(myDelegates, myDelegate1);              myDelegates = (MyDelegate)Delegate.Combine(myDelegates, myDelegate2);              myDelegates = (MyDelegate)Delegate.Combine(myDelegates, myDelegate3);

在第一行代碼中,由于此時myDelegates是null,因而當Delegate.Combine方法發現要合并的是null和一個委托實例myDelegate1時,Delegate.Combine會直接返回myDelegate1的值,因而第一行代碼執行完畢之后,myDelegates現在引用了myDelegate1所引用的委托實例。

當第二次調用Delegate.Combine方法,繼續合并myDelegates和myDelegate2的時候,Delegate.Combine方法檢測到myDelegates已經不再是null而是引用了一個委托實例,此時Delegate.Combine方法會構建一個不同于myDelegates和myDelegate2的新的委托實例。這個新的委托實例自然會對上文常常提起的_target和_methodPtr這兩個私有字段進行初始化,但是此時需要注意的是,之前一直沒有實際值的_invocationList字段此時被初始化為一個對委托實例數組的引用。該數組的第一個元素便是包裝了第一個委托實例myDelegate1所引用的PrintNum方法的一個委托實例(即myDelegates此時所引用的委托實例),而數組的第二個元素則是包裝了第二個委托實例myDelegate2所引用的PrintDoubleNum方法的委托實例(即myDelegate2所引用的委托實例)。之后,將這個新創建的委托實例的引用賦值給myDelegates變量,此時myDelegates指向了這個包裝了兩個回調方法的新的委托實例。

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

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

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

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

當Delegate.Remove方法被調用時,它會從后向前掃描myDelegates所引用的委托實例中的委托數組,并且對比委托數組中的元素的_target字段和_methodPtr字段的值是否與第二個參數即新建的MyDelegate委托類的實例中的_target字段和_methodPtr字段的值匹配。如果匹配,且刪除該元素之后,委托實例數組中只剩余一個元素,則直接返回該元素(委托實例);如果刪除該元素之后,委托實例數組中還有多個元素,那么就會創建一個新的委托實例,這個新創建的委托實例的_invocationList字段會引用一個由刪除了目標元素之后剩余的元素所組成的委托實例數組,之后返回該委托實例的引用。當然,如果刪除匹配實例之后,委托實例數組變為空,那么Remove就會返回null。需要注意的一點是,Remove方法每次僅僅移除一個匹配的委托實例,而不是刪除所有和目標委托實例匹配的委托實例。

當然,如果每次合并委托和刪除委托都要寫Delegate.Combine和Delegate. Remove則未免顯得太過繁瑣,所以為了方便使用C#語言的開發者,C#編譯器為委托類型的實例重載了+=和-+操作符來對應Delegate.Combine和Delegate. Remove。具體的例子,我們可以看看下面的這段代碼。

using UnityEngine;using System.Collections; public class MulticastScript : MonoBehaviour{    delegate void MultiDelegate();    MultiDelegate myMultiDelegate;         void Start ()    {        myMultiDelegate += PowerUp;        myMultiDelegate += TurnRed;               if(myMultiDelegate != null)        {            myMultiDelegate();        }    }       void PowerUp()    {        print ("Orb is powering up!");    }       void TurnRed()    {        renderer.material.color = Color.red;    }}

好,我想到此我已經回答了本小節題目中所提出的那個問題:委托是如何調用多個方法的。但是為了要實現觀察者模式甚至是我們自己的消息系統,還有一個大人物不得不介紹,那就是和委托關系密切的事件,那么下一篇博客就讓我們走進委托和事件的世界中吧。


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
久久香蕉精品香蕉| 中文字幕欧美精品日韩中文字幕| 在线观看欧美日韩| 国产精品美女久久久久av超清| 成人激情视频在线| 精品国产一区二区三区在线观看| 粉嫩老牛aⅴ一区二区三区| 久久精品这里热有精品| 亚洲视频日韩精品| 国语自产偷拍精品视频偷| 国产精品狼人色视频一区| 国产欧美在线视频| 日本高清不卡在线| 亚洲欧美国产精品| 一本大道亚洲视频| 日韩欧美在线国产| 91精品国产777在线观看| 亚洲第一av网| 中文字幕av一区二区| 国产精品久久激情| 亚洲最新av在线| 91精品在线播放| 91精品国产高清自在线看超| 国产91成人video| 久热99视频在线观看| 国产一区二区三区直播精品电影| 一本色道久久88综合亚洲精品ⅰ| 成人中文字幕+乱码+中文字幕| 成人国产精品一区二区| 欧美性极品xxxx娇小| 欧美国产精品人人做人人爱| 亚洲国产天堂久久国产91| 少妇av一区二区三区| 日韩中文在线中文网在线观看| 日韩二区三区在线| 亚洲精品丝袜日韩| 91久久久久久久久久久久久| 亚洲色图综合久久| 精品视频久久久| 国产精品丝袜高跟| 亚洲国产一区自拍| 亚洲高清在线观看| 伊人精品在线观看| 久久中国妇女中文字幕| 久久精品久久久久久国产 免费| xxav国产精品美女主播| 中文字幕精品久久| 国产女人精品视频| 欧美激情免费观看| 国产亚洲成精品久久| 中文字幕日韩电影| 久久精品国产v日韩v亚洲| 国产精品久久久久久av福利软件| 国产精品成人国产乱一区| 亚洲国产精品成人av| 日韩电影中文字幕在线观看| 欧美怡红院视频一区二区三区| 欧美日韩国产精品一区二区三区四区| 日韩欧美黄色动漫| 亚洲精品电影在线观看| 国产精品大片wwwwww| 粉嫩老牛aⅴ一区二区三区| 久久亚洲精品国产亚洲老地址| 欧美俄罗斯性视频| 色先锋资源久久综合5566| 欧美三级xxx| 亚洲国产小视频在线观看| 亚洲成人免费在线视频| 色偷偷偷亚洲综合网另类| 国产97免费视| 欧美高清在线视频观看不卡| 欧美日韩国产中字| 国产日本欧美一区二区三区| 亚洲国产高清自拍| 亚洲电影第1页| 欧美激情视频给我| 欧美激情中文字幕乱码免费| 欧美精品久久久久久久| 欧美日韩午夜剧场| 国产a级全部精品| 国产视频亚洲视频| 91精品国产成人| 一区二区三区视频免费| 成人黄色免费网站在线观看| 91免费看国产| 色哟哟亚洲精品一区二区| 国产va免费精品高清在线| 欧美成人sm免费视频| 北条麻妃一区二区三区中文字幕| 欧美日韩国产在线播放| 一区二区欧美激情| 欧美激情手机在线视频| 午夜精品久久久久久久99黑人| 久久免费视频在线| 亚洲人在线视频| 亚洲男人第一av网站| 欧美激情国产精品| 久久成人人人人精品欧| 亚洲人成人99网站| 2021国产精品视频| 欧美成人亚洲成人日韩成人| 亚洲精品久久在线| 俺也去精品视频在线观看| 国产日韩欧美视频在线| 欧美视频专区一二在线观看| 少妇久久久久久| 精品女同一区二区三区在线播放| 国产精品视频在线观看| 亚洲成人性视频| 91久久中文字幕| 国产精品久久久亚洲| 欧美日韩成人在线播放| 91免费视频网站| 九九精品在线观看| 亚洲xxxxx性| 日韩欧美成人精品| 欧美韩国理论所午夜片917电影| 最近日韩中文字幕中文| 国产精品视频自在线| 中文字幕亚洲欧美日韩高清| 国产精品久久不能| 亚洲人精选亚洲人成在线| 国产精品久久77777| 久久精品免费播放| 久久艹在线视频| 91精品久久久久久久久久久| 91精品国产综合久久久久久蜜臀| 久久国产精品久久久| 国产精品揄拍500视频| 亚洲精品中文字幕av| 欧美性xxxx极品hd满灌| 亚洲免费电影一区| 性色av香蕉一区二区| 日韩美女视频免费在线观看| 亚洲香蕉在线观看| 久久资源免费视频| 欧美一级片久久久久久久| 久久精品视频在线观看| 亚洲欧美激情视频| 国产精品久久久av久久久| 色噜噜狠狠色综合网图区| 亚洲a区在线视频| 日本精品中文字幕| 国产不卡视频在线| 国产精品揄拍一区二区| 国产精品永久免费观看| 亚洲黄色www网站| 日韩第一页在线| 97av视频在线| 亚洲国产欧美一区二区三区同亚洲| 亚洲一区www| 日韩成人av一区| 精品国产自在精品国产浪潮| 亚洲女人初尝黑人巨大| 国产精品视频自拍| 日韩欧美国产免费播放| 欧美激情xxxxx| 日本欧美国产在线| 国产精品一区专区欧美日韩| 激情成人在线视频| 狠狠久久亚洲欧美专区| 亚洲毛片在线观看| 亚洲欧美另类人妖| 国产精品流白浆视频|