C#基礎原理拾遺——面試都愛問的委托和事件(糾正)


      這篇博客是我昨天寫的,文中的觀點有些問題,后經過網友留言和個人學習發現錯誤,原文還是保留,更改補在后面,不怕貽笑大方,唯恐誤人子弟。不知道還能不能放在首頁,讓被誤導的同學再被反誤導一次。

一、原文

      幾乎所有的面試題都會問:事件是委托嗎,說說委托和事件的聯系和區別?每次答這個題都很蛋疼,因為把它們的關系說簡單了就描述不准確,想說清楚就不是一兩句話的事了。我通常在回答中加這么一句:委托與事件的關系好比字段與屬性的關系。很多人理解它們的關系時也做這樣的類比,雖然簡單一句話概括了它們的關系,但總不能讓我感到滿意。

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM