眾所周知在面試中,經常有些崽子面試官會問些“事件和委托”的關系,也許一路走來的程序員大多都會被問到這個,那么對于這個
高頻的”事件和委托“問題,如何回擊呢?首先我從最經典的一套面試題說起,用事件來實現 “貓爪老鼠“,這是一個從網上copy過來的一
個例子。
static void Main(string[] args) { Mouse mouse = new Mouse(); Cat cat = new Cat(); cat.OnCry(); Console.ReadLine(); } } public delegate void CryEventHandler(); public class Cat { public static event CryEventHandler Cry; public Cat() { Console.WriteLine("Cat:I'm coming."); } public virtual void OnCry() { Console.WriteLine("Cat:MiaoMiao"); if (Cry != null) { Cry.Invoke(); } } } public class Mouse { public Mouse() { Cat.Cry += new CryEventHandler(Run); Console.WriteLine("Mouse:I go to find something,and I must always listen cat's crying."); } public void Run() { Console.WriteLine("Mouse:A cat is coming,I must go back!"); } }
事件定義啥的什么玩意這個我就不說了,沒什么意思,為了了解這個跟委托有什么關系,下面我們來看看這段代碼最后生成的IL是什么樣的。
1:CryEventHandler委托
1 public delegate void CryEventHandler();
這個我想大家都清楚,委托本質上是一個繼承于MulticastDelegate的類,同時會生成僅有的4個方法,看下IL即知。
2:Cat類
1 public class Cat 2 { 3 public static event CryEventHandler Cry; 4 5 public Cat() 6 { 7 Console.WriteLine("Cat:I'm coming."); 8 } 9 10 public virtual void OnCry() 11 { 12 Console.WriteLine("Cat:MiaoMiao"); 13 if (Cry != null) 14 { 15 Cry.Invoke(); 16 } 17 } 18 }
從這個類中,我們看到了一個Cry事件,然后就是一個Cry.Invoke(),不過當你看到Invoke的時候,你是不是很懷疑Cry是不是一個委托字段呢?
其實你懷疑的是一點問題都沒有,32個贊,看下IL。
從上圖中我們看到了兩個好玩的東西:
① field Cry 字段,完整定義如下,然來所謂的“事件字段” 其實在IL下面蛻變成了委托字段,如果你覺得很奇怪,請看第二點。
.field private static class Sample.CryEventHandler Cry
② add_Cry,remove_Cry,如果僅僅將事件字段變成委托字段,那確實是編譯器在發什么神經,然來編譯器還給事件配備了兩個方法,這個
其實也就是事件里面+=,-=的奧秘所在,下面我們挑add_Cry方法說下,看看方法定義的IL代碼是怎么樣的。
很新奇,我們找到了Combine方法,這個我們都知道,原來事件中的+=,其實就是利用Combine來將當前的委托實例放到Delegate的
委托鏈表中(其實里面是array實現的),為了方便理解,我把上面的IL代碼翻譯成C#代碼。
1 public class Cat 2 { 3 /// <summary> 4 /// 私有的委托變量 5 /// </summary> 6 private static CryEventHandler Cry; 7 8 /// <summary> 9 /// 事件生成的方法 10 /// </summary> 11 /// <param name="cryEventHandler"></param> 12 public void Add_Cry(CryEventHandler cryEventHandler) 13 { 14 var result = (CryEventHandler)Delegate.Combine(Cry, cryEventHandler); 15 16 Interlocked.CompareExchange<CryEventHandler>(ref Cry, result, Cry); 17 } 18 19 public void Remove_Cry(CryEventHandler cryEventHandler) 20 { 21 var result = (CryEventHandler)Delegate.Remove(Cry, cryEventHandler); 22 23 Interlocked.CompareExchange<CryEventHandler>(ref Cry, result, Cry); 24 } 25 26 public Cat() 27 { 28 Console.WriteLine("Cat:I'm coming."); 29 } 30 31 public virtual void OnCry() 32 { 33 Console.WriteLine("Cat:MiaoMiao"); 34 35 if (Cry != null) 36 { 37 //委托專用的四個方法,invoke,begininvoke,endinvoke,ctor 38 Cry.Invoke(); 39 } 40 } 41 }
可能有些同學對IL指令不是很熟悉,沒關系,我也一樣,咱博客園上面有位大神飛鳥的一篇IL指令集的博文或許能幫得到你。
3:Mouse類
如果你對Cat類的IL代碼琢磨的差不多的話,下面這個Mouse類就非常簡單了,僅僅調用而已嘛。
1 public class Mouse 2 { 3 public Mouse() 4 { 5 Cat.Cry += new CryEventHandler(Run); 6 Console.WriteLine("Mouse:I go to find something,and I must always listen cat's crying."); 7 } 8 9 public void Run() 10 { 11 Console.WriteLine("Mouse:A cat is coming,I must go back!"); 12 } 13 }
這個地方最讓人關心的就是:Cat.Cry += new CryEventHandler(Run) 這個語句,從它的IL中可以看到其實做了三件事。
① ldftn: 將Run方法的非托管指針推送到計算堆棧上。
② newobj: 將CryEventHandler委托new一下,同時將計算堆棧上的Run方法的非托管指針作為構造函數的參數。
③ call: 調用Cat類的Add_Cry方法,將CryEventHandler的實例作為參數傳遞下去。
下面繼續將該IL代碼反編譯回來,不過針對IL指令:call void Sample.Cat::add_Cry(class Sample.CryEventHandler)
并沒有很好的翻譯過來,只能new Cat()了一下才能調用Add_Cry,從而觸發了Cat的構造函數。
1 public class Mouse 2 { 3 public Mouse() 4 { 5 var cryHandler = new CryEventHandler(Run); 6 7 /* 8 * 針對IL:call void Sample.Cat::add_Cry(class Sample.CryEventHandler) 9 * 這個沒有反編譯好,因為我new Cat()將會再次調用構造函數。 10 */ 11 new Cat().Add_Cry(cryHandler); 12 13 Console.WriteLine("Mouse:I go to find something,and I must always listen cat's crying."); 14 } 15 16 public void Run() 17 { 18 Console.WriteLine("Mouse:A cat is coming,I must go back!"); 19 } 20 }
好了,說了這么多,應該也有總結性的東西出來了,原來事件是完完全全的建立在委托的基礎上,你可以認為事件就是用委托來玩一個
觀察者模式的,你甚至可以認為事件就是委托。沒有本質區別。
文章列表