1. 程式人生 > >工作十餘年,還是一直被問 委託和事件 有什麼區別? 真是夠了

工作十餘年,還是一直被問 委託和事件 有什麼區別? 真是夠了

## 一:背景 ### 1. 講故事 前幾天公司一個妹子問我,事件和委託有什麼區別? 先由衷感嘆一下,編碼十餘年,年輕的時候常被面試官問起,現在年長了,卻被後輩們時常問候,看樣子逃離編碼生涯之前是跑不掉了,不過奇怪的是,這個問題被問起的時候,我發現有很多人用: `事件是一種特殊的委託` 來進行總結,是不是挺有意思,我想這句話可能來自於網路上的面試題答案吧,這篇我就試著徹底總結一下。 ## 二:事件真的是特殊的委託嗎? ### 1. 貓和老鼠 經典案例 要想知道兩者到底什麼關係? 先得有一些基礎程式碼,這裡就用大家初學事件時用到的 `貓和老鼠` 經典案例,程式碼簡化如下: ``` C# class Program { static void Main(string[] args) { Cat cat = new Cat("湯姆"); Mouse mouse1 = new Mouse("傑瑞", cat); Mouse mouse2 = new Mouse("傑克", cat); cat.CatComing(); Console.ReadKey(); } } class Cat { public event Action CatCome; //宣告一個事件 private string name; public Cat(string name) { this.name = name; } public void CatComing() { Console.WriteLine("貓" + name + "來了"); CatCome?.Invoke(); } } class Mouse { private string name; public Mouse(string name, Cat cat) { this.name = name; cat.CatCome += this.RunAway; //Mouse 註冊 CatCome 主題 } public void RunAway() { Console.WriteLine(name + "正在逃跑"); } } ``` ![](https://img2020.cnblogs.com/other/214741/202008/214741-20200810091751440-1380024445.png) 程式碼非常簡潔,貓的 CatCome 動作一旦觸發,註冊到 CatCome 上的 兩隻 mouse 就會執行各自的逃跑動作 `RunAway`,如果大家沒有看懂可以多看幾遍哈。 ### 2. 觀察者模式/釋出訂閱模式 如果你瞭解過設計模式,我想你應該第一眼就能看出這是 觀察者模式,對的,現在無數的框架都在使用這個模式,比如前端的: Vue,Knockout,React,還有redis的釋出訂閱等等,如果用圖畫一下大概就是這樣。 ![](https://img2020.cnblogs.com/other/214741/202008/214741-20200810091751679-655637403.png) 從圖中可以看到,幾個 subscribe 都訂閱了一個叫做 subject 的主題,一旦有外來的 publish 推送到了 subject,那麼訂閱 subject 的 subscribe 都會收到通知,接下來根據這張圖對剛才的程式碼再縷一篇: * 貓的 ` public event Action CatCome` 就是一個主題 (subject)。 * 老鼠的 `cat.CatCome += this.RunAway` 就是 subscribe 對 subject 的訂閱。 * 最後的 `public void CatComing()` 就是對 subject 的推送, pubish了一條 `貓來了`。 ### 3. 使用觀察者模式 對 貓鼠進行解剖 有了觀察者模式的基礎,對上面的程式碼進行改造就方便多了, 我可以把 `public event Action CatCome;` 改成 一個 `List` 陣列,模擬 `Subject` 哈,簡化後的程式碼如下: ``` C# class Cat { public List Subject = new List(); //定義一個主題 private string name; public Cat(string name) { this.name = name; } public void CatComing() { Console.WriteLine("貓" + name + "來了"); Subject.ForEach(item => { item.Invoke(); }); } } class Mouse { private string name; public Mouse(string name, Cat cat) { this.name = name; cat.Subject.Add(RunAway); //將 逃跑 方法注入到 subject 中 } public void RunAway() { Console.WriteLine(name + "正在逃跑"); } } ``` ![](https://img2020.cnblogs.com/other/214741/202008/214741-20200810091751919-1723398447.png) 看到這裡,我想你對 `事件和委託` 應該有一個大概的認識了吧,但這裡還有一個問題,C#中的事件 真的如我寫的觀察者模式這樣的嗎??? 要回答這個問題,需要從 IL 角度看一下事件到底生成了什麼。 ## 三:從IL角度看事件 ### 1. 使用 ilspy /ildasm 小工具 首先來看一下所謂的事件到底在 IL 層面是個什麼東西,如下圖: ![](https://img2020.cnblogs.com/other/214741/202008/214741-20200810091752208-58274968.png) 從圖中看其實就是兩個接收 `Action` 引數的 `add_CatCome`和 `remove_CatCome`方法,這兩個方法簡化後的 il 程式碼如下: ``` C# .event [mscorlib]System.Action CatCome { .addon instance void ConsoleApp2.Cat::add_CatCome(class [mscorlib]System.Action) .removeon instance void ConsoleApp2.Cat::remove_CatCome(class [mscorlib]System.Action) } .method public hidebysig specialname instance void add_CatCome ( class [mscorlib]System.Action 'value' ) cil managed { // Method begins at RVA 0x2090 // Code size 41 (0x29) .maxstack 3 .locals init ( [0] class [mscorlib]System.Action, [1] class [mscorlib]System.Action, [2] class [mscorlib]System.Action ) IL_0000: ldarg.0 IL_0001: ldfld class [mscorlib]System.Action ConsoleApp2.Cat::CatCome IL_0006: stloc.0 // loop start (head: IL_0007) IL_000b: call class [mscorlib]System.Delegate [mscorlib]System.Delegate::Combine(class [mscorlib]System.Delegate, class [mscorlib]System.Delegate) IL_0010: castclass [mscorlib]System.Action IL_0017: ldflda class [mscorlib]System.Action ConsoleApp2.Cat::CatCome IL_001e: call !!0 [mscorlib]System.Threading.Interlocked::CompareExchange(!!0&, !!0, !!0) // end loop IL_0028: ret } // end of method Cat::add_CatCome .method public hidebysig specialname instance void remove_CatCome ( class [mscorlib]System.Action 'value' ) cil managed { IL_0000: ldarg.0 IL_0001: ldfld class [mscorlib]System.Action ConsoleApp2.Cat::CatCome IL_0006: stloc.0 // loop start (head: IL_0007) IL_000b: call class [mscorlib]System.Delegate [mscorlib]System.Delegate::Remove(class [mscorlib]System.Delegate, class [mscorlib]System.Delegate) IL_0010: castclass [mscorlib]System.Action IL_0017: ldflda class [mscorlib]System.Action ConsoleApp2.Cat::CatCome IL_001e: call !!0 [mscorlib]System.Threading.Interlocked::CompareExchange(!!0&, !!0, !!0) IL_0026: bne.un.s IL_0007 // end loop IL_0028: ret } // end of method Cat::remove_CatCome ``` 接下來看看 `mouse` 類的註冊是怎麼實現的。 ![](https://img2020.cnblogs.com/other/214741/202008/214741-20200810091752519-1804791285.png) 從圖中可以看到,所謂的註冊就是將 `RunAway` 作為 `add_CatCome` 方法的引數傳進去而已,回過頭來看,最核心的就是那兩個所謂的 `addxxx` 和 `removexxx` 方法。 ### 2. 將IL程式碼進行C#還原 可能有些同學對 IL 程式碼不是很熟悉,如果能還原成 C# 程式碼就