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,今天先到這兒。如有不足的地方,望見諒。