天天看點

轉載:C#中事件的由來

原文位址 http://www.tracefact.net/CSharp-Programming/Delegates-and-Events-in-CSharp.aspx 感謝部落客分享!

我們繼續思考轉載:C# 中的委托中的例子程式:上面的三個方法都定義在Programe類中,這樣做是為了了解的友善,實際應用中,通常都是 GreetPeople 在一個類中,ChineseGreeting和 EnglishGreeting 在另外的類中。現在你已經對委托有了初步了解,是時候對上面的例子做個改進了。假設我們将GreetPeople()放在一個叫GreetingManager的類中,那麼新程式應該是這個樣子的:

namespace Delegate {
    //定義委托,它定義了可以代表的方法的類型
    public delegate void GreetingDelegate(string name);
    
    //建立的GreetingManager類
    public class GreetingManager{
       public void GreetPeople(string name, GreetingDelegate MakeGreeting) {
           MakeGreeting(name);
       }
    }

    class Program {
       private static void EnglishGreeting(string name) {
           Console.WriteLine("Morning, " + name);
       }

       private static void ChineseGreeting(string name) {
           Console.WriteLine("早上好, " + name);
       }

       static void Main(string[] args) {
           // ... ...
        }
    }
}      

這個時候,如果要實作前面示範的輸出效果,Main方法我想應該是這樣的:

static void Main(string[] args) {
    GreetingManager gm = new  GreetingManager();
    gm.GreetPeople("Jimmy Zhang", EnglishGreeting);
    gm.GreetPeople("張子陽", ChineseGreeting);
}      

我們運作這段代碼,嗯,沒有任何問題。程式一如預料地那樣輸出了:

Morning, Jimmy Zhang

早上好, 張子陽

現在,假設我們需要使用轉載:C# 中的委托中學到的知識,将多個方法綁定到同一個委托變量,該如何做呢?讓我們再次改寫代碼:

static void Main(string[] args) {
    GreetingManager gm = new  GreetingManager();
    GreetingDelegate delegate1;
    delegate1 = EnglishGreeting;
    delegate1 += ChineseGreeting;

    gm.GreetPeople("Jimmy Zhang", delegate1);
}      

輸出:

Morning, Jimmy Zhang

早上好, Jimmy Zhang

到了這裡,我們不禁想到:面向對象設計,講究的是對象的封裝,既然可以聲明委托類型的變量(在上例中是delegate1),我們何不将這個變量封裝到 GreetManager類中?在這個類的用戶端中使用不是更友善麼?于是,我們改寫GreetManager類,像這樣:

public class GreetingManager{
    //在GreetingManager類的内部聲明delegate1變量
    public GreetingDelegate delegate1;  

    public void GreetPeople(string name, GreetingDelegate MakeGreeting) {
       MakeGreeting(name);
    }
}      

現在,我們可以這樣使用這個委托變量:

static void Main(string[] args) {
    GreetingManager gm = new  GreetingManager();
    gm.delegate1 = EnglishGreeting;
    gm.delegate1 += ChineseGreeting;

    gm.GreetPeople("Jimmy Zhang", gm.delegate1);
}      

輸出為:

Morning, Jimmy Zhang

早上好, Jimmy Zhang

盡管這樣做沒有任何問題,但我們發現這條語句很奇怪。在調用gm.GreetPeople方法的時候,再次傳遞了gm的delegate1字段:

gm.GreetPeople("Jimmy Zhang", gm.delegate1);      

既然如此,我們何不修改 GreetingManager 類成這樣:

public class GreetingManager{
    //在GreetingManager類的内部聲明delegate1變量
    public GreetingDelegate delegate1;  

    public void GreetPeople(string name) {
        if(delegate1!=null){     //如果有方法注冊委托變量
          delegate1(name);      //通過委托調用方法
       }
    }
}      

在用戶端,調用看上去更簡潔一些:

static void Main(string[] args) {
    GreetingManager gm = new  GreetingManager();
    gm.delegate1 = EnglishGreeting;
    gm.delegate1 += ChineseGreeting;

    gm.GreetPeople("Jimmy Zhang");      //注意,這次不需要再傳遞 delegate1變量
}      

輸出為:

Morning, Jimmy Zhang

早上好, Jimmy Zhang

盡管這樣達到了我們要的效果,但是還是存在着問題:

在這裡,delegate1和我們平時用的string類型的變量沒有什麼分别,而我們知道,并不是所有的字段都應該聲明成public,合适的做法是應該public的時候public,應該private的時候private。

我們先看看如果把 delegate1 聲明為 private會怎樣?結果就是:這簡直就是在搞笑。因為聲明委托的目的就是為了把它暴露在類的用戶端進行方法的注冊,你把它聲明為private了,用戶端對它根本就不可見,那它還有什麼用?

再看看把delegate1 聲明為 public 會怎樣?結果就是:在用戶端可以對它進行随意的指派等操作,嚴重破壞對象的封裝性。

最後,第一個方法注冊用“=”,是指派文法,因為要進行執行個體化,第二個方法注冊則用的是“+=”。但是,不管是指派還是注冊,都是将方法綁定到委托上,除了調用時先後順序不同,再沒有任何的分别,這樣不是讓人覺得很别扭麼?

現在我們想想,如果delegate1不是一個委托類型,而是一個string類型,你會怎麼做?答案是使用屬性對字段進行封裝。

于是,Event出場了,它封裝了委托類型的變量,使得:在類的内部,不管你聲明它是public還是protected,它總是private的。在類的外部,注冊“+=”和登出“-=”的通路限定符與你在聲明事件時使用的通路符相同。

我們改寫GreetingManager類,它變成了這個樣子:

public class GreetingManager
    {        
public event GreetingDelegate MakeGreet(string name);

        public void GreetPeople(string name)
        {
            MakeGreet(name);
        }
    }      

很容易注意到:MakeGreet 事件的聲明與之前委托變量delegate1的聲明唯一的差別是多了一個event關鍵字以及delegate1的聲明多了參數類型。看到這裡,在結合上面的講解,你應該明白到:事件其實沒什麼不好了解的,聲明一個事件不過類似于聲明一個進行了封裝的委托類型的變量而已。

為了證明上面的推論,如果我們像下面這樣改寫Main方法:

static void Main(string[] args) {
    GreetingManager gm = new  GreetingManager();
    gm.MakeGreet = EnglishGreeting;         // 編譯錯誤1
    gm.MakeGreet += ChineseGreeting;

    gm.GreetPeople("Jimmy Zhang");
}      

會得到編譯錯誤:事件“Delegate.GreetingManager.MakeGreet”隻能出現在 += 或 -= 的左邊(從類型“Delegate.GreetingManager”中使用時除外)。