C# 委託淺析

C# 中的委託(Delegate)類似於 C 或 C++ 中函數的指針。委託(Delegate) 是存有對某個方法的引用的一種引用類型變量。引用可在運行時被改變。

委託(Delegate)特別用於實現事件和回調方法。所有的委託(Delegate)都派生自 System.Delegate 類。

委託的聲明(沒有方法體的函數加上delegate關鍵字):

        /// <summary>
        /// 無參委託
        /// </summary>
        public delegate void DelegaetNoReturnPara();
        /// <summary>
        /// 有參委託
        /// </summary>
        /// <param name="num"></param>
        public delegate void DelegatePara(int num);
        /// <summary>
        /// 有參帶返回值的委託
        /// </summary>
        /// <param name="num"></param>
        public delegate int DelegateParaNumber(int num);
        /// <summary>
        /// 泛型委託
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="t"></param>
        /// <returns></returns>
        public delegate T DelegateGeneric<T>(T t);

我們用反編譯工具查看一下

 用反編譯工具可以看到委託是一個密閉的方法並且繼承於[System.Runtime]System.MulticastDelegate,委託有一個構造函數,帶有Invoke、BeginInvoke、EndInvoke三個方法。

委託既然是類,那我們也可對其進行實例化

        public static void GetData(int num)
        {
            Console.WriteLine($"調用了{nameof(GetData)}方法");
        }

        public delegate void DelegatePara(int num);

        static void Main(string[] args)
        {
            Customer.GetData(1);
//委託的實例化必須傳遞一個方法,這個方法的返回值和參數必須和實例化的這個委託一致
//把方法包裝成變量,Invoke的時候自動執行方法 DelegatePara noReturnPara
= new DelegatePara(Customer.GetData);
noReturnPara.Invoke(1);//調用委託
noReturnPara(1);
}

 在這裏可能有同學會想,爲什麼不直接調用方法呢?ok,接着往下看.

有一個student類和一個獲取集合對象的一個方法.

public class Student
{
    public string name { get; set; }
    public int age { get; set; }
    public string sex { get; set; }
}

public async Task<List<Student>> GetAllStudents()
{
    var result = new List<Student>();
    for (int i = 1; i < 10; i++)
    {
        result.Add(new Student
        {
            age = 17 + i,
            name = $"橘貓{i}",
            sex = i / 2 == 0 ? "" : ""
        });
    }
    return result;
}

假如現在有三個判斷條件篩選集合中的內容

/// <summary>
/// 查詢name的長度大於2的同學
/// </summary>
/// <returns></returns>
public async Task<List<Student>> GetStudents()
{
    var result = new List<Student>();
    var data = await this.GetAllStudents();
    foreach (var item in data)
    {
        if (item.name.Length > 2)
            result.Add(item);
    }
    return result;
}
/// <summary>
/// 查詢年齡大於18歲的同學
/// </summary>
/// <returns></returns>
public async Task<List<Student>> GetStudents()
{
    var result = new List<Student>();
    var data = await this.GetAllStudents();
    foreach (var item in data)
    {
        if (item.age > 18)
            result.Add(item);
    }
    return result;
}
/// <summary>
/// 查詢年齡大於18歲並且是女生的同學
/// </summary>
/// <returns></returns>
public async Task<List<Student>> GetStudents()
{
    var result = new List<Student>();
    var data = await this.GetAllStudents();
    foreach (var item in data)
    {
        if (item.age > 18 && item.sex.Equals(""))
            result.Add(item);
    }
    return result;
}

三個判斷條件要寫三個方法是不是太冗餘了,我們直接寫在一個方法裏,根據條件類型來篩選數據

/// <summary>
/// 根據type來查詢
/// </summary>
/// <returns></returns>
public async Task<List<Student>> GetStudents(int type)
{
    var result = new List<Student>();
    var data = await this.GetAllStudents();
    switch (type)
    {
        case 1:
            foreach (var item in data)
            {
                if (item.name.Length > 2)
                    result.Add(item);
            }
            break;
        case 2:
            foreach (var item in data)
            {
                if (item.age > 18)
                    result.Add(item);
            }
            break;
        case 3:
            foreach (var item in data)
            {
                if (item.age > 18 && item.sex.Equals(""))
                    result.Add(item);
            }
            break;
        default:
            Console.WriteLine("查詢的類型有誤,請重新輸入");
            break;
    }
    return result;
}

這樣的話行是行,但是如果在多加一個條件呢?那麼我們可不可以將判斷邏輯傳到方法裏,只需返回true或false就行了.

//定義委託
public
delegate bool judge(Student student);
//用來判斷姓名
public bool judgeName(Student student) { return student.name.Length > 2; }
//用來判斷年齡
public bool judgeAge(Student student) { return student.age > 18; }
//用來判斷年齡和性別
public bool judgeAgeAndSex(Student student) { return student.age > 18 && student.sex.Equals(""); }
/// <summary>
/// 將判斷邏輯傳遞過來
/// 委託解耦,減少重複代碼,將公共邏輯當成變量傳遞過來
/// </summary>
/// <returns></returns>
public async Task<List<Student>> GetStudents(judge judge)
{
    var result = new List<Student>();
    var data = await this.GetAllStudents();
    foreach (var item in data)
    {
        if (judge.Invoke(item))
            result.Add(item);
    }
    return result;
}

有同學會說,你這代碼也不精簡,爲什麼不用lamda.這裏的話主要是想告訴大家委託的作用,側重點不同,望理解。

多播委託:

public delegate void DelegatePara(int num);
//這個委託實例只包含一個方法
DelegatePara noReturnPara = new DelegatePara(Customer.GetData);
//+= 爲委託實例按順序增加方法,形成方法鏈,依次執行
DelegatePara noReturnPara = new DelegatePara(Customer.GetData);
noReturnPara += new DelegatePara(Customer.GetData);
noReturnPara += new DelegatePara(new DelegateInstance().GetData);
noReturnPara.Invoke(1);//調用委託
//-= 爲委託實例移除方法,從方法鏈的底部開始匹配,遇到第一個完全吻合的,移除且只移除一個
noReturnPara += new DelegatePara(Customer.GetData);
noReturnPara += new DelegatePara(new DelegateInstance().GetData);//這個去不掉,因爲不是同一個實例中的方法
//循環獲得委託中的實例
foreach (var item in noReturnPara.GetInvocationList())
{
    item.DynamicInvoke(1);//調用委託
}

多播委託如果帶返回值,結果以最後的爲準。

事件:是帶event關鍵字的委託實例,event可以限制變量被外部調用/直接賦值

public delegate void HandleEvent(int num);
public event HandleEvent handleEvent;
public void DelegateEvent()
{
    handleEvent = new HandleEvent(Customer.GetData);
    handleEvent.Invoke(1);
}

如果不是在本身所在的類中調用(事件)會如何呢?

 很明顯報錯了

只能像下面這樣寫

DelegateInstance delegateInstance = new DelegateInstance();
delegateInstance.handleEvent += new DelegateInstance.HandleEvent(Customer.GetData);
Console.ReadKey();

這裏需要說一下:Event+委託的一個實例,加上一個event關鍵字限制了外部調用權限,保證其安全(不能再其它類中調用和賦值,只能用來添加和移除註冊方法),在本身類中可以調用和賦值,但是在子類中也不可以調用和賦值.

委託與事件的區別和聯繫:委託是一個類型(委託的本質是一個類);事件是委託類型的一個實例.

ok,今天先到這兒。如有不足的地方,望見諒。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章