淺談委託和事件(一)

關於委託和事件,多是.NET或者說是面向對象編程語言中的一個比較重要又比較難以理解的概念。關於這一話題,園子裏的人也寫了不少文章,最經典的可能就是張子陽的C#中的委託和事件這兩篇文章了,以前也看過MSDN 上的WebCast深刻 "委託和事件"。可能和不少人同樣,剛開始讀的時候,以爲很清楚,可是過了一段時間好像又忘記了委託和事件的區別,知道好久之前,在一次面試中我被問到委託和事件有什麼區別,一會兒就說不清了。html

因此這裏稍微理一下,也算是本身的一個總結。固然,仍是推薦你們先讀前面推薦的兩篇文章。面試

.NET中的事件模型是創建在委託(delegate)這一機制上的,因此首先來看看什麼是委託。編程

委託

委託是一種類型安全的調用回調方法,相似於C中的函數指針。委託(Delegate)是一個類,當建立實例時,須要傳入方法名稱,每個委託都有一個簽名,好比:安全

delegate int SomeDelegate(string s, bool b);

這就是一個委託簽名,他能夠表明第一個參數類型爲string類型,第二個參數類型爲bool型,而且返回值爲int的全部方法。編程語言

當建立代理實例時,須要傳入一個方法名稱做爲構造函數。這個方法必須和代理定義的參數和返回值類型相同。好比:函數

private static int SomeFunction(string str, bool bln){...}

這樣咱們就能夠實例化一個SomeDelegate,並將SomeFunction做爲參數傳遞進去了,由於它們有相同的簽名:this

SomeDelegate sd = new SomeDelegate(SomeFunction);

如今sd指向了SomeFunction這個函數。若是調用sd,那麼SomeFucntion函數就會被調用。spa

sd("SomeString", true);

事件

定義了事件成員的類型容許類型或者類型的實例在某些特定事情發生時,通知其餘對象。在日常的編程中,咱們不經意間已經接觸到了事件,好比Button類,當點擊時,就會觸發Click事件,Timer類,每一秒就會觸發tick事件。要了解事件,最簡單的方法就是舉個例子來講明。舉個通俗的貓叫,老鼠跑,主人被驚醒的例子。這也是面試時容易被問到的問題。解決方法就是使用事件,或者擴展開來就是觀察者模式。線程

首先來實現Cat類,具體實現以下:代理

public class Cat
{
    public delegate void MeowHandler(object sender, EventArgs s);
    public event MeowHandler Meow;

    protected virtual void OnMeow(EventArgs s)
    {
        MeowHandler temp = Meow;
        if (temp != null)
        {
            temp(this, s);
        }
    }

    public void StartMeow()
    {
        EventArgs args = new EventArgs();
        Console.WriteLine("Cat start meow...");
        OnMeow(args);
    }
}

首先定義了一個public名爲MeowHandler的代理,表示貓叫。方法簽名很簡單,一個名爲Sender的Object參數和一個EventArgs的參數,返回值爲空。接着定義一個名爲Meow的事件。注意這裏定義事件使用event參數,而後加一個代理的名稱。定義成public供外部註冊。

緊接着,咱們定義一個觸發貓叫的方法OnMeow,用來通知全部已訂閱Meow事件的對象。這裏爲了線程安全,在內部定義了一個temp對象,來保存Meow委託。若是不適用臨時變量temp,方法只隱約Meow的話,在線程知道Meow不爲null的時候,另一個線程講NewMail改成null,這樣就會引起異常。

將OnMeow定義爲虛方法使得派生類能夠直接控制事件是否發生。一般派生類只須要調用基類的OnMeow方法便可。可是在有些狀況下,派生類能夠選擇不調用基類的OnMeow方法。

最後,定義了一個StartMeow方法,用來將外部的輸入轉換爲引起事件的動做。當咱們調用StartMeow事,就會調用OnMeow方法,模擬事件發生。

接下來,須要定義Mouse類型:

public class Mouse
{
    public Mouse(Cat cat)
    {
        cat.Meow += new Cat.MeowHandler(cat_Meow);
    }

    void cat_Meow(object sender, EventArgs s)
    {
        Console.WriteLine("Mouse run...");
    }

    public void Unregister(Cat cat)
    {
        cat.Meow -= cat_Meow;
    }
}

當初始化Mouse對象時,經過構造函數傳入Cat對象,而後註冊Cat的Meow事件。

最後,實例化一個Cat,而後將其做爲參數傳入Mouse的構造函數。當Cat的StartMeow方法調用時,Mouse的註冊的Meow事件就會觸發。

static void Main(string[] args)
{
    Cat c = new Cat();
    Mouse m = new Mouse(c);
    c.StartMeow();
    Console.ReadKey();
}

區別

能夠看到,對於事件,咱們只能使用+=,和-=來進行註冊和取消註冊,沒法來使用=進行賦值。在內部+=被轉化爲在委託鏈表上增長委託,-=被轉化爲在委託立案表上移除委託。限制了用戶可以直接操做委託的權限。好比,若是沒有事件,經過代理的話,只有把代理定義爲public,這樣,當某個用戶已經註冊了一系列的委託到委託列表上,正要執行的時候,另一個用戶使用賦值語句=,給予了一個新的委託,或者直接將其設置爲null,那麼前一個用戶的委託列表就會被丟失,這樣嚴重破壞了對象的封裝性。Event使得只能使用+=和-=操做符來進行事件的註冊和取消註冊。

對照字段和屬性,委託和事件關係彷佛也是如此,它是對委託實例的一層封裝,使得客戶端不能隨意更改或者重置內部的委託列表,只可以容許往列表中增長或者移除委託,使得代碼具備更好的封裝性。這樣來看,事件其實只不過相似聲明瞭一個進行了封裝的委託類型變量而已。

總結

本篇文章簡單介紹了委託和事件的概念,算是本身作個筆記,更好的相關介紹推薦看以前兩位前輩寫的文章。下一篇文章將介紹事件內部的處理機制、事件與線程安全等。

相關文章
相關標籤/搜索