同事今天問了個有趣的問題:

using System;
 
namespace ConsoleApplication1
{
    class Program
    {
        public class Boo
        {
            public delegate void MyDelegate(int i);
            //兩個delegate,其中一個加註event關鍵字
            public MyDelegate DelegateTest;
            public event MyDelegate EventTest;
            //在Test()中觸發兩個delegate
            public void Test()
            {
                int i = DateTime.Now.Second;
                if (DelegateTest != null) DelegateTest(i);
                if (EventTest != null) EventTest(i);
            }
        }
        static void Main(string[] args)
        {
            Boo boo = new Boo();
            //兩種做法都可用+=方式掛上多個事件
            boo.DelegateTest += (i) =>
            {
                Console.WriteLine("Delegate A: {0}", i); 
            };
            boo.DelegateTest += (i) =>
            {
                Console.WriteLine("Delegate B: {0}", i);
            };
            boo.EventTest += (i) =>
            {
                Console.WriteLine("Event A: {0}", i);
            };
            boo.EventTest += (i) =>
            {
                Console.WriteLine("Event B: {0}", i);
            };
            boo.Test();
            Console.Read();
        }
    }
}

依之前的理解,Class上宣告事件的目的在提供呼叫端能於特定時機觸發自訂邏輯,標準做法是先定義委派型別(Delegate),再宣告一個公開委派型別變數並加上event關鍵字,就算是為類別定義出了事件,之後呼叫端便能用+=, -=加上或移除事件邏輯,實現"事件"的運作概念。(參考)

不過,在文章一開頭的程式範例,宣告委派型別後我們定義了兩個公開變數,不管有沒有加註event關鍵字,一樣都可用+=掛上多個函數,同樣都能用if (someDelegate != null) someDelegate();的寫法觸發,看起來加不加event關鍵字並不影響其運作效果,那麼,加上event關鍵字的意義到底是什麼?

過去在專案裡已套用過不少event設計,卻回答不出這個問題,自己的"不知其所以然"事項又再記上一筆,著實汗顏。所幸,很快找到一篇精闢的文章解答了我的疑惑:

事實上,宣告event與否,並不會讓編譯出的程式本體有所不同(該文作者動用IL反組譯證明了這點),但使用event關鍵字會產生以下四點差異:

  1. 只有透過加註event,該委派型別變數才能被當成介面(Interface)定義的一部分,這是一般的欄位(Field)變數無法辦到的,此點可算event關鍵字最主要的功能。
  2. 另外還有一點很大差異,委派型別變數加上event後,就只能透過類別內部的程式碼呼叫,不允許外部直接執行之。以上述程式為例,執行boo.DeletateTest();是OK的,但boo.EventTest();光在編譯時就過不了關,會出現以下錯誤: The event 'ConsoleApplication1.Program.Boo.EventTest' can only appear on the left hand side of += or -= (except when used from within the type 'ConsoleApplication1.Program.Boo')
  3. 相較於屬性(Property)有get; set;,加上event後,我們可以實作add; remove;,可在呼叫端加掛或移除事件時執行自訂邏輯。
  4. 在.NET Framework裡,所有宣告event關鍵字的事件,應遵循someEvent(object source, EventArgs e)的參數慣例,但自行開發時的事件,編譯器或IDE倒不會強制檢查就是了。

對於event的了解又多了一些,真好~~


Comments

# by 熱血青年在哪裡

之前曾看到一個說法,分享一下,一樣的意思,他是這樣說的: 增加了封裝性,在外部可以看到DelegateTest的一些屬性、方法,甚至拿到他的掛載函式清單(GetInvocationList)等等,但event就將這些完全封裝了起來,約略可以想成event就是一個private的作用。

# by motovb

我是用 field & property 的關係 去想 delegate 跟 event 的

Post a comment