--- tags: C#進階VIP班 --- # 玩轉C#之【委派和事件】 ## 委派的宣告、實體化和使用 宣告一個委派: 在一個沒有方法體的方法前面加上delegate ```csharp= public delegate void NoReturnNoPara<T>(T t); public delegate void NoReturnNoPara(); public delegate void NoReturnWithPara(int x,int y); public delegate int WithReturnNoPara(); public delegate string WithReturnWithPara(out int x,ref int y); ``` 透過IL反編譯工具,我們可以觀察到實際上 * 宣告委託實際上是屬於一種class * 他繼承父類MulticastDelegate * 可以看到.ctor=>建構子 * 裡面有Invoke、BeginInvoke、EndInvoke,這些方法 ![IL反編譯](https://i.imgur.com/caFb9nr.png) 總結: 委託是一個類,繼承自System.MulticastDelgate,裡面內置了幾個方法 一般使用類別的方式: ```csharp= Student student =new Student() { Id = 123, Name ="上課不乖", Age = 32, ClassId =1 }; sudent.Study(); ``` 使用一個委託: 當呼叫`method.Invoke()`的時候,實際上就只是執行一次`DoNothing()` ```csharp= NoReturnNoPara method = new NoReturnNoPara(this.DoNothing);//實體化一個委託 method.Invoke();//使用他的方法 method();//也可以省略Invoke的寫法 ``` ```csharp= private void DoNothing() { Console.log("Test"); } ``` 從目前的實做中,委託的價值看起來是 把方法包裝成變量,等到invoke的時候自動執行方法 範例題目: ```csharp= #region 資料準備 /// <summary> /// 資料準備 /// </summary> /// <returns></returns> private List<Student> GetStudentList() { #region 初始化資料 List<Student> studentList = new List<Student>() { new Student() { Id=1, Name="老K", ClassId=2, Age=35 }, new Student() { Id=1, Name="hao", ClassId=2, Age=23 } #endregion return studentList; } #endregion ``` 當有一些需求是: * 希望找到年齡大於25學生 * 希望找到名字長度大於2的學生 * 希望找出名字長度大於2且年齡大於25班級Id是2的學生 ```csharp= List<Student> studentList = this.GetStudentList(); //找出年齡大於25學生 List<Student> result = new List<Student>();//准备容器 foreach (Student student in studentList)//遍历数据源 { if (student.Age > 25)//判断条件 { result.Add(student);//满足条件的放入容器 } } Console.WriteLine($"结果一共有{result.Count()}个"); //找到名字長度大於2的學生 List<Student> result = new List<Student>(); foreach (Student student in studentList) { if (student.Name.Length > 2) { result.Add(student); } } //找出名字長度大於2且年齡大於25班級Id是2的學生 List<Student> result = new List<Student>(); foreach (Student student in studentList) { if (student.Name.Length > 2 && student.Age > 25 && student.ClassId == 2) { result.Add(student); } } Console.WriteLine($"结果一共有{result.Count()}个"); ``` 我們會發現這三個都在做重複的邏輯 1. 準備容器 2. 尋遍資料 3. 判斷條件 4. 滿足條件的塞入容器 我們來優化一下程式碼: * 最簡單的方法 type:不同的值 不同的判断 如果有新的需求又需要再多一種 * 传个参数--判断参数--执行对应的行为 * 那我能不能直接判斷條件呢?邏輯就是方法,等于说傳遞方法近來 ```csharp= private List<Student> GetList(List<Student> source, int type) { List<Student> result = new List<Student>(); foreach (Student student in source) { if (type == 1) { if (student.Age > 25)//判断条件 { result.Add(student);//满足条件的放入容器 } } else if (type == 2) { if (student.Name.Length > 2) { result.Add(student); } } else if (type == 3) { if (student.Name.Length > 2 && student.Age > 25 && student.ClassId == 2) { result.Add(student); } } } return result; } ``` 第二種方式: * 把判斷邏輯傳遞近來,透過委託傳遞近來 * 委託解偶=>可以減少重複程式碼 ```csharp= public delegate bool ThanDelegate(Student student); private bool than(Student student) { return student.Age > 25; } ThanDelegate method = new ThanDelegate(than); List<Student> result = this.GetListDelegate(studentList, method); private List<Student> GetListDelegate(List<Student> source, ThanDelegate method) { List<Student> result = new List<Student>();//準備容器 foreach (Student student in source) { if (method.Invoke(student)) { result.Add(student); } } return result; } ``` 工作中的應用: ```csharp= public static void SafeInvoke(Action act) { try { avt.Invoke(); } catch(Exception ex) { Console.WriteLine(ex.Message); } } ``` ## 泛型委派--Func Action .Net Framework 3.0 Linq/task ```csharp= public class ActionFunc { public void Show() { //Action 0~16個參數的 沒有返回值 泛型 委派 Action action =()=>{}; Action<int> action2 = i=>Console.WriteLine(i); //Func 0~16個參數的 有返回值 泛型 委派 Func<int> func1 = () =>1; Func<int,string> func2 = i=>i.ToString(); } } ``` 為什麼要 Func Action 很多委派長的一樣 但是不能通用 會導致不同框架中 定義各種各樣的相同委派 就是浪費 所以為了統一 就全部使用標準的Action Func ```csharp= private void DoNothing(Action act) { act.Invoke(); } ``` ## 委派的意義:解偶 ## 委派的意義:非同步多執行緒 ```csharp= WithReturnNoPara method = new WithReturnNoPara(this.GetSomething); int iResult = method.Invoke(); iResult = method(); var result = method.BeginInvoke(null,null);//非同步 method.EndInvoke(result); ``` ```csharp= private int GetSomething() { return 1; } ``` ## 委派的意義:Multicast Delegates(合併委派) 價值:可以在一個變量保存多個方法,可以增減,invoke的時候可以按順序執行 * +=為委派增加實體按順序增加方法,形成方法鏈,Invoke時,按順序依次執行 * -=為委派移除方法,從方法鏈的尾部開始匹配,遇到第一個完全吻合的,移除且只移除一個,沒有也不會異常 ```csharp= NoReturnNoPara method = new NoreturnNoPara(this.DoNothing); method += new NoreturnNoPara(this.DoNothing); method += new NoReturnNoPara(DoNothingStatic); method += new NoReturnNoPara(Student.StudenAdvanced); method += new NoReturnNoPara(new Student().Study); method.Invoke(); method -= new NoReturnNoPara(DoNothingStatic); method -= new NoReturnNoPara(Student.StudenAdvanced); method -= new NoReturnNoPara(new Student().Study); method.Invoke(); ``` 疑!!我們會發現,這裡的方法怎麼會沒有被移除呢? * 因為不是同一個實體,所以是不同的方法 ![移除方法](https://i.imgur.com/TrwzNYS.png) :notebook: 小提示: 合併委派是不能使用非同步的,會報錯誤 ```csharp= method.BeginInvoke(null,null); ``` 找出合併委派中裡面的所有方法 ```csharp= foreach(var item in method.GetInvocationList()) { item.Invoke(); } ``` 如果合併委派帶返回值,結果以最後的為準 ```csharp= WithReturnNoPara method = new WithReturnNoPara(this.GetSomething); method += new WithReturnNoPara(this.GetSomething2); method += new WithReturnNoPara(this.GetSomething3); int iResult = method.Invoke();//結果會是GetSomething3的數值 ``` ## 事件 事件:是帶event關鍵字的委派的實體,可以限制變數被外部直接使用/直接賦予值(安全保障) 特點: * 不能直接Invoke() * 不能 賦予 null => = null 委派和事件的區別和關係? * 委派是一個類型 * 事件是委派類型的一個實例 事件:可以把一堆可變的動作/行為封裝出去,交給第三方指定 預定義一樣,程式設計的時候,我們可以把程式分成兩部分 一部分是固定的,直接寫死,還有不固定的地方,通過一個事件去開放接口,外部可以隨意地擴展動作 框架:完成固定/通用部分,把可變部分留出擴展點,支持自定義 觀察者模式: ![](https://rjgeek.github.io/images/2016/12/Observer_1.png?t=1%3E) ## 小作業: SQL CRUD 試試使用委派