前幾天公司一個妹子問我,事件和委託有什麼區別? 先由衷感嘆一下,編碼十餘年,年輕的時候常被面試官問起,如今年長了,卻被後輩們時常問候,看樣子逃離編碼生涯以前是跑不掉了,不過奇怪的是,這個問題被問起的時候,我發現有不少人用: 事件是一種特殊的委託
來進行總結,是否是挺有意思,我想這句話可能來自於網絡上的面試題答案吧,這篇我就試着完全總結一下。前端
要想知道二者到底什麼關係? 先得有一些基礎代碼,這裏就用你們初學事件時用到的 貓和老鼠
經典案例,代碼簡化以下:面試
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
,若是你們沒有看懂能夠多看幾遍哈。redis
若是你瞭解過設計模式,我想你應該第一眼就能看出這是 觀察者模式,對的,如今無數的框架都在使用這個模式,好比前端的: 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了一條 貓來了
。有了觀察者模式的基礎,對上面的代碼進行改造就方便多了, 我能夠把 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 層面是個什麼東西,以下圖:ide
從圖中看其實就是兩個接收 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 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<class [mscorlib]System.Action>(!!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<class [mscorlib]System.Action>(!!0&, !!0, !!0) IL_0026: bne.un.s IL_0007 // end loop IL_0028: ret } // end of method Cat::remove_CatCome
接下來看看 mouse
類的註冊是怎麼實現的。oop
從圖中能夠看到,所謂的註冊就是將 RunAway
做爲 add_CatCome
方法的參數傳進去而已,回過頭來看,最核心的就是那兩個所謂的 addxxx
和 removexxx
方法。
可能有些同窗對 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 中的方法就會被執行,也就看到兩隻老鼠在逃跑啦。
您如今是否是明白啦,委託和事件的關係 比如 磚頭和房子的關係,房子只是磚頭的一個應用場景,您若是說房子是一種特殊的磚,這句話品起來是否是有一種怪怪的感受,不是嗎?