工作十余年,还是一直被问 委托和事件 有什么区别? 真是够了
一:背景
1. 講故事
前幾天公司一個妹子問我,事件和委托有什么區別?先由衷感嘆一下,編碼十余年,年輕的時候常被面試官問起,現在年長了,卻被后輩們時常問候,看樣子逃離編碼生涯之前是跑不掉了,不過奇怪的是,這個問題被問起的時候,我發現有很多人用:?事件是一種特殊的委托?來進行總結,是不是挺有意思,我想這句話可能來自于網絡上的面試題答案吧,這篇我就試著徹底總結一下。
二:事件真的是特殊的委托嗎?
1. 貓和老鼠 經典案例
要想知道兩者到底什么關系?先得有一些基礎代碼,這里就用大家初學事件時用到的?貓和老鼠?經典案例,代碼簡化如下:
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 + "正在逃跑");}}代碼非常簡潔,貓的 CatCome 動作一旦觸發,注冊到 CatCome 上的 兩只 mouse 就會執行各自的逃跑動作?RunAway,如果大家沒有看懂可以多看幾遍哈。
2. 觀察者模式/發布訂閱模式
如果你了解過設計模式,我想你應該第一眼就能看出這是 觀察者模式,對的,現在無數的框架都在使用這個模式,比如前端的:Vue,Knockout,React,還有redis的發布訂閱等等,如果用圖畫一下大概就是這樣。
從圖中可以看到,幾個 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<Action>?數組,模擬?Subject?哈,簡化后的代碼如下:
class Cat{public List<Action> Subject = new List<Action>(); //定義一個主題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 + "正在逃跑");}}看到這里,我想你對?事件和委托?應該有一個大概的認識了吧,但這里還有一個問題,C#中的事件 真的如我寫的觀察者模式這樣的嗎???要回答這個問題,需要從 IL 角度看一下事件到底生成了什么。
三:從IL角度看事件
1. 使用 ilspy /ildasm 小工具
首先來看一下所謂的事件到底在 IL 層面是個什么東西,如下圖:
從圖中看其實就是兩個接收?Action?參數的?add_CatCome和?remove_CatCome方法,這兩個方法簡化后的 il 代碼如下:
.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 specialnameinstance 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.0IL_0001: ldfld class [mscorlib]System.Action ConsoleApp2.Cat::CatComeIL_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.ActionIL_0017: ldflda class [mscorlib]System.Action ConsoleApp2.Cat::CatComeIL_001e: call !!0 [mscorlib]System.Threading.Interlocked::CompareExchange<class [mscorlib]System.Action>(!!0&, !!0, !!0)// end loopIL_0028: ret } // end of method Cat::add_CatCome.method public hidebysig specialnameinstance void remove_CatCome (class [mscorlib]System.Action 'value') cil managed {IL_0000: ldarg.0IL_0001: ldfld class [mscorlib]System.Action ConsoleApp2.Cat::CatComeIL_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.ActionIL_0017: ldflda class [mscorlib]System.Action ConsoleApp2.Cat::CatComeIL_001e: call !!0 [mscorlib]System.Threading.Interlocked::CompareExchange<class [mscorlib]System.Action>(!!0&, !!0, !!0)IL_0026: bne.un.s IL_0007// end loopIL_0028: ret } // end of method Cat::remove_CatCome接下來看看?mouse?類的注冊是怎么實現的。
從圖中可以看到,所謂的注冊就是將?RunAway?作為?add_CatCome?方法的參數傳進去而已,回過頭來看,最核心的就是那兩個所謂的?addxxx?和?removexxx?方法。
2. 將IL代碼進行C#還原
可能有些同學對 IL 代碼不是很熟悉,如果能還原成 C# 代碼就????????了,接下來我就試著還原一下。
class Cat{Action CatCome;public void add_CatCome(Action value){Action action = this.CatCome;Action action2 = null;do{action2 = action;Action value2 = (Action)Delegate.Combine(action2, value);action = Interlocked.CompareExchange(ref this.CatCome, value2, action2);}while ((object)action != action2);}public void remove_CatCome(Action value){Action action = this.CatCome;Action action2 = null;do{action2 = action;Action value2 = (Action)Delegate.Remove(action2, value);action = Interlocked.CompareExchange(ref this.CatCome, value2, action2);}while ((object)action != action2);}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.add_CatCome(this.RunAway);}public void RunAway(){Console.WriteLine(name + "正在逃跑");}}可以看出還原后的C#代碼跑起來是沒有問題的,和觀察者模式相比,這里貌似沒有看到?subject?這樣的?List<Action>?集合,但是你仔細分析的話,其實是有的,你一定要著重分析這句代碼:?Action value2 = (Action)Delegate.Combine(action2, value);?它用的就是多播委托,用?Combine?方法將后續的 Action 送到前者Action的?_invocationList?中,不信的話,我調試給你看哈。
沒毛病吧,?Action CatCome?中已經有了兩個 callback 方法啦,一旦?CatCome.Invoke(), _invocationList 中的方法就會被執行,也就看到兩只老鼠在逃跑啦。
四:總結
您現在是不是明白啦,委托和事件的關系 好比 磚頭和房子的關系,房子只是磚頭的一個應用場景,您如果說房子是一種特殊的磚,這句話品起來是不是有一種怪怪的感覺,不是嗎?
總結
以上是生活随笔為你收集整理的工作十余年,还是一直被问 委托和事件 有什么区别? 真是够了的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 修复被破坏的 vs 工程设置(续)
- 下一篇: SwaggerUI看烦了,IGeekFa