天天看點

C#深入學習委托

1.向SendMessage和BroadcastMessage說再見

首先我們知道小标題所說的兩個方法的作用是通過參數名來進行方法的調用,但是這兩個函數過于依賴反射機制來查找消息對應的被調用的函數,頻繁的使用反射機制會影響性能能,更加嚴重的問題是,使用這種機制後代碼的維護成本太高,加入在一個開發團隊中如果某一個方法被重新命名甚至被删除,會導緻很嚴重的隐患,并且在編譯器嚴重,他是沒有錯誤的,是以也不會通過一些錯誤的提示來告訴開發者某些方法不存在,另外在一個團隊的開發中,方法的命名如果一樣呢,這也是個很大的隐患,而且所述的這幾種隐患在平常都是不會發生的,隻有當執行特定的功能的時候,發現找不到或者識别不了是那個函數的時候才會發現,但是這個時候已經太遲了。

2.認識回調函數—委托

先上代碼:

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Blog_test : MonoBehaviour
{
    // Start is called before the first frame update
    public delegate void MyDelegate(int number);
    MyDelegate myDelegate;
    void Start()
    {
        myDelegate += printNumber;
        myDelegate += doubleNumber;
        myDelegate(3);
    }

    private void doubleNumber(int number)
    {
        Debug.Log("doubleNumber number" + 2 * number);
    }

    private void printNumber(int number)
    {
        Debug.Log("printNumber number:" + number);
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

           

如果和使用委托?

1.聲明委托類型,其執行個體和引用的方法

2.注冊委托的方法

3.方法内的邏輯

注意,上述代碼的委托類型是空,當然也可以是任意類型,委托要确定一個回調方法簽名,參數以及傳回類型等,在上述例子中回調方法的參數類型是int,傳回類型是void

将此腳本挂載到u3d上的物體上,運作遊戲會直接輸出

C#深入學習委托

通過這樣的機制,在Start函數中綁定幾個回調函數,在某個觸發事件函數中,通過調用myDelegare(參數)的方式通知已經注冊委托的回調函數,令其執行相應的程式,比如說,在某個遊戲場景中英雄損失血量,這個時候,如果要多個功能一起實作,如,UI血量損失的變化、英雄被擊打的動畫播放如果采用委托機制,這些功能可以互不幹擾的執行,體驗會更好一點。

當然涉及到委托的還有它的另一個特點-委托參數的逆變性,和委托傳回類型的協變性

逆變性:方法擷取的參數類型可以是委托參數的類型的基類

協變性:方法的傳回類型可以是從委托的傳回類型派生的一個派生類,例如在我們的項目中存在基礎機關類(BaseUnitCalss)、士兵類(SoldierClass)以及英雄類(HeroClass),其中基礎類作為積累派生出士兵類和英雄類,那麼可以這樣定義一個委托

public delegate object TellMeYouName(SolderClass solder);
    TellMeYouName TellMeYou;
    void Start()
    {
        TellMeYou += TellMeYourNameMethod;
       
    }
    private string TellMeYourNameMethod(BaseUnitClass baser)
    {
        throw new NotImplementedException();
    }
           

由于TellMeYourNameMethod的傳回值類型是String,是派生于TellMeYourName委托的傳回值Object的,是以這種傳回類型的協變性也是可以的,參數也是一樣,一個是基類,一個是派生類,這樣的協變性也是可以的

注意:

協變性和逆變性都是隻支援引用類型的,是以如果值類型或者void是不可以這樣做的,例如将

string TellMeYourNameMethod(BaseUnitClass baser)
    {
        throw new NotImplementedException();
    }
           

修改成

int TellMeYourNameMethod(BaseUnitClass baser)
    {
        throw new NotImplementedException();
    }
           

雖然兩者都派生出obect但是int是值類型是以編譯器報錯,至于什麼類型是值類型,什麼類型是引用類型可以百度

3.委托是如何實作的
internal delegate void MyDelegate(int number);
    MyDelegate myDelegate = new MyDelegate(myMethon1);
    myDelegate = myMethon2;
    myDelegate(2);
           

從表面上看,委托似乎很簡單,使用new構造MyDelegate委托執行個體,通過構造函數建立的執行個體此時引用的方法是myMethod1,然後通過方法組替換為myMethod2,最後調用回調函數,看上去真的很簡單 。

實際上編譯器為MyDelegate類定義了四個方法,一個構造器、Invoke、BeginInvoke、EndInvoke。而MyDelegate類本省又派生字基礎類庫種定義的System.MulticastDelegate類,是以可以推斷出所有的委托類型都派生自System.MulticastDelegate,但是這個類又派生于類外一個類——System.Delegate,是以我們在使用委托的時候可能會用到Delegate種的方法,例如Combine、Remove(現在被+=、-=重載)。

上文提到所有的委托都派生于MulticastDelege,是以自然會繼承其屬性、字段、方法,對于委托而言,有三個字段是很重要i的

字段 作用
_targe 儲存回調方法要操作的對象
_methodPtr 内部的整數值,用來辨別回調方法
_invocationList 如果回調函數不值一個(構造委托鍊),他會引用一個數組,将回調函數的引用加入數組,當委托調用回調函數的時候,依次調用數組種的回調函數

需要注意的一點,所有委托都有一個擷取兩個參數的構造方法,這兩個方法分别是對象的引用和IntPro類型的用來引用回調函數的句柄

是以在注冊回調函數的時候,c#編譯器會分析代碼,來确定引用的是那個放發和那個對象,然後将對象的引用傳遞給object參數,而方法的引用傳遞給method參數,當這兩個參數傳遞給構造函數的時候,會被_target和_methodPtr這兩個私有字段儲存,用來唯一辨別要回調的函數。

4.委托是如何調用多個回調函數的

上文中有一個字段_invocationList,起作用是如果注冊了多個回調函數,每個回調函數都會有自己的_targe、_methodPtr,

myDelegate += printNumber;
 myDelegate += doubleNumber;
 myDelegate(2);
           

第一行代碼種,此時myDelegate為NULL,執行完第一行代碼,會建立一個委托執行個體,調用Delegate.Combine傳回myDelegate的值,是以第一行執行結束後,myDelegate引用printNumber;

執行第二行的時候,繼續建立一個不同于printNumber委托執行個體,調用Delegate.Combine,此時_targe、_methodPtr依舊是專屬辨別和引用,但是_invocationList被初始化為一個委托實力數組的引用。

執行myDelegate(2)的時候,由于會飲食的執行Invoke方法,當Invoke發現_invocationList不再是null而是引用了一個委托執行個體的數組,他會執行一個循環,按照順序調用每個委托執行個體.

參考教材unity3d腳本程式設計

繼續閱讀