這篇博客是我昨天寫的,文中的觀點有些問題,后經過網友留言和個人學習發現錯誤,原文還是保留,更改補在后面,不怕貽笑大方,唯恐誤人子弟。不知道還能不能放在首頁,讓被誤導的同學再被反誤導一次。
一、原文
幾乎所有的面試題都會問:事件是委托嗎,說說委托和事件的聯系和區別?每次答這個題都很蛋疼,因為把它們的關系說簡單了就描述不准確,想說清楚就不是一兩句話的事了。我通常在回答中加這么一句:委托與事件的關系好比字段與屬性的關系。很多人理解它們的關系時也做這樣的類比,雖然簡單一句話概括了它們的關系,但總不能讓我感到滿意。
1、委托與事件到底什么關系?
當我們談委托與事件的關系時,是說委托這種類型和事件這種類型的關系呢,還是具體的委托對象和事件對象之間的關系?我以為是前者。那么委托和事件是兩種類型,而字段和屬性是具體的對象,雖然都是封裝,我覺得兩者之間還是有區別的,事件對委托的封裝是在類級別的、抽象層次、穩定的封裝,而屬性對字段的封裝是在對象級別的、具體的、可自定義的封裝。(個人理解,后來證明有偏差)由此造成的最直觀的區別就是,在發布者類中使用事件時,不需要提供對應的委托對象;而在類中使用屬性時,一般要提供對應的字段讓屬性來進行封裝。
Reflector查看類之間的繼承關系如下:
MSDN中有這么一句:“事件是特殊類型的多路廣播委托,僅可從聲明它們的類或結構(發行者類)中調用。”如此看來事件與委托的關系應該是繼承關系,在繼承的過程中在EventHandler類中進行的封裝。
以上是從理論的角度分析了事件和委托的關系,下面通過代碼加強認識。
2、委托模擬事件
實現一個非常簡單的功能:控制台輸入一個數,如果輸入100的話就顯示”Game Over”。用事件和委托模擬的事件分別實現該功能。
class Program { static void Main(string[] args) { //事件實現 Game1 game = new Game1(); game.GameOverEvent += game_GameOver; ////委托實現 //Game2 game = new Game2(); //game.AddMethod(game_GameOver); while (true) { Console.WriteLine("輸入:"); game.Scroe = Convert.ToInt32(Console.ReadLine()); } } static void game_GameOver(object sender, EventArgs e) { Console.WriteLine("Game Over"); } static void game_GameOver() { Console.WriteLine("Game Over"); } } //事件實現 class Game1 { //event 關鍵字用於在發行者類中聲明事件。 public event EventHandler GameOverEvent; private int scroe; public int Scroe { get { return scroe; } set { this.scroe = value; if (this.GameOverEvent != null) { if (value == 100) { this.GameOverEvent(this, new EventArgs());//觸發事件 } } } } } //委托實現 class Game2 { public delegate void OverDelegate(); //將委托聲明為private,防止訂閱者直接調用,使用new等功能 private OverDelegate GameOverDelegate; //AddMethod和RemoveMethod模擬事件的+=和-=賦值 public void AddMethod(OverDelegate over) { this.GameOverDelegate += over; } public void RemoveMethod(OverDelegate over) { this.GameOverDelegate -= over; } private int scroe; public int Scroe { get { return scroe; } set {
this.scroe = value;
if (this.GameOverDelegate != null) {
if (value == 100) { this.GameOverDelegate();//觸發委托 } } } } }
以上Game2類中,委托對事件的模擬即是在對象級別做的封裝,AddMethod和RemoveMethod方法是在Game2類中實現的,而不是在OverDelegate委托中實現的,因此事件的模擬依賴了OverDelegate和Game2兩個類。而Game1中EventHandler類本身就封裝了委托,限制了其在外部(訂閱者)的實例化,這種功能的實現沒有依賴於Game1。所以事件的這種內部封裝機制減少了依賴,符合松耦合要求。
以上是我個人的一點理解,有失偏頗之處還望批評指正。
然而EventHandler內部是怎么封裝的呢?我的思路尚不清晰,Reflector查看也沒看出個所以然,我自己會認真學習探索,在此也向各位請教,希望大家能告訴我答案或提供一些思路。為謝!
二、改正
3、其實不是EventHandler一人的功勞
在上文中我以為事件對委托的封裝是EventHandler類一人的功勞,其實沒有深入理解的話很容易這么想,因為在聲明事件的時候並沒有聲明對應的委托,直觀上就感覺這個封裝是發生在EventHandler類內部的。看了 @小AI 給我推薦的http://www.cnblogs.com/JimmyZhang/archive/2007/09/23/903360.html一文,發現問題所在。
在Reflector中反編譯上面的例子,Game1類的結構如下:
Game1在此就是發布者類,對事件GameOverEvent的封裝還是在發布者類中完成的,並不是在EventHandler類中就已經完成。這種封裝模式和屬性對字段的封裝模式是完全一樣的,只不過這個封裝過程是由於有了event關鍵字在編譯階段由編譯器自動完成的,在發布者類中將EventHandler類型的GameOverEvent委托設為私有,並加上Add和Remove方法,和上面用委托模擬的事件是一樣的,也就是事件的本質就是在小標題2中用委托模擬的事件。這么說來,事件對委托的封裝是可以類比為屬性對字段的封裝的,如果說有區別,那就是前者是編譯階段完成的、不可自定義的封裝,后者是coding階段實現的、可自定義的封裝,但它們都是在對象級別完成的。
雖然這次出了錯,但並沒有使我灰心,相反,如果我不把自己的想法拿出來和大家交流,可能問題永遠得不到解決。唯一的弊端就是大膽的寫自己的想法,如果是錯誤的話,很可能誤導比我還菜的同學,我能做的就是盡量多方求證,並且出錯后及時改正。也勉勵和我一樣的菜鳥多寫博客,別怕暴漏錯誤,大家對於大牛中規中矩的blog已經看的boring了,小菜錯誤的想法里可能偶爾就有着不被規則約束的創新。hehe


