C# 基礎知識系列- 11 委託和事件

0. 前言

事件和委託是C#中的高級特性,也是C#中很有意思的一部分。出現事件的地方,必然有委託出現;而委託則不一定會有事件出現。那為什麼會出現這樣的關係呢?這就需要從事件和委託的定義出發,了解其中的內在。

1. 委託

說起委託,就不得不回憶一下之前在Linq篇中介紹的匿名方法,其中提到了Func和Action這兩個類型。這兩個類型就是委託。

委託在C#中定義為一種面向對象形式的方法定址方案。簡單來講,就是定義一個類型,然後表示這個類型代表某一種方法。而委託對象,就是方法參數化。委託可以實現將方法當做一個參數傳遞給另一個方法,也可以認為是反射中的MethodInfo的一種特例(實際上並沒有太多關係)。

委託不關心方法叫什麼,也不關心方法從哪來(歸屬於哪個類或者哪個對象),只關心方法需要哪些參數,返回什麼類型。

說到這裡,我們來看一下如何定義一個委託吧,委託的定義形式如下:

delegate <返回類型>  委託名(參數列表);//參數列表代表任意個參數

由之前的定義形式,我們可以知道委託也是一種類型,所以它的定義也符合類型的定義規範。現在我們定義一個沒有返回值也沒有參數類型的委託作為我們創建的第一個委託:

public delegate void FirstDel();// 類型名稱是 FirstDel

簡單的使用一下:

FirstDel del ;
del();// 會直接報錯

上述程式碼如果運行的話,會很直接的報錯,因為你沒有告訴編譯器變數del 應該是什麼,也就是沒有為del賦值,同時委託可以賦值為null,所以在使用的時候需要注意不能為null,否者也是無法運行的。

這裡應用匿名方法的話,可以按照下面的程式碼對del進行賦值:

del = ()=>
{
 	//省略方法   
}

那麼我們熱身結束,開始正式創建一個有意義的委託:

public delegate decimal CalculateArea(decimal height, decimal weight);

上述委託聲明了一個計算面積的規範,使用長寬進行面積計算,那麼我們來為它賦值:

CalculateArea squrare = (height, weight) => height * height;// 正方形
CalculateArea rectangle = (height, weight) => height * weight;// 矩形
CalculateArea triangle = (height, weight) => height * weight / 2; //三角形

我們依次創建了三個計算面積的方法,分別是正方形、矩形、三角形,分別調用它們將會得到對應的計算結果:

var squrareArea = squrare(10, 10);// 100
var rectangleArea = rectangle(19, 10);//190
var triangleArea = triangle(10, 5);//25

特別的,C#中委託支援多路廣播,所以也可以使用+-進行註冊和刪除。多路廣播是指在事件和委託中有多個監聽器或響應方法,當事件觸發或者委託調用的時候,註冊的方法組將會都調用。當使用這種方式對委託進行賦值的時候,委託將自動轉為方法組,簡單理解就是 委託對象內部創建了一個列表,然後把賦值給它的方法都存進去了。

所以就會產生如下操作:

CalculateArea calculate = squrare;// calculate必須先賦值一個方法
calculate += rectangle;// 增加 矩形的面積計算方法
calculate += triangle; // 增加三角形的面積計算方法
calculate -= triangle; // 減去三角形的面積計算方法

到這裡會產生一個疑問,calculate運行結果是什麼,會返回一個數組或者其他類型嗎?顯然不會,因為calculate定義的返回類型就是一個decimal,所以不會返回其他的值。

嗯,這就產生了另一個疑問,返回的是哪一個方法的計算結果呢,其他方法的計算結果呢?這裡告訴大家一個結果,只會返回最後一次註冊的方法的執行結果,其他的方法執行了,但是方法的執行結果無法用變數接到。

所以這裡有一個很重要的實踐,如果有需要把委託當做一個方法列表進行使用的時候,最好聲明為void或者拋棄返回值的具體內容。

2. 事件

事件,event。在C#中,事件就像是一種機制,在程式運行到一定階段的時候或者遇到某些狀況的時候,就會觸發一個事件。然後如果有其他程式碼訂閱了這個事件,就會自動執行訂閱的程式碼。描述起來很抽象,簡單來講就是在類聲明一個委託,並標記這個委託是一個事件,在另一個方法中執行這個事件。其中,觸發這個事件的類稱為發布者,接受或者註冊了處理方法的類稱為訂閱者。

如何創建或聲明一個事件?聲明一個事件有兩種方式,一種是直接使用EventHandler ,另一種是自己先定義一個委託,然後用這個委託定義事件。

1. 使用EventHandler

public class EventDemo
{
    public event EventHandler HandlerEvent;
}

2. 使用自定義委託

public class EventDemo
{
    public delegate void EventDelegate(object sender, EventArgs e);
    public event EventDelegate DelegateEvent;
}

一般事件的定義約定俗稱是一個void方法,第一個參數是sender表示事件的發布者,默認是object類型,第二個參數是EventArgs類型的事件變數,表示觸發事件時需要訂閱者注意的內容,一般用來傳一些參數。

其中 EventHandler有一個泛型版本,其聲明如下:

public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);

其第二個參數並沒有對TEventArgs進行限制,所以我們可以用任何類型當做事件變數。

我們再來看看,EventArgs里有什麼,什麼都沒有,只有一個默認構造方法和幾個繼承自Object的方法。所以在開發中,我們會自己定義一個事件變數類型,為了保持一致會繼承EventArgs。

C#建議事件的定義以On開頭,表示在什麼時觸發,示例程式碼並不符合這個規範。

3. 使用一下事件和委託

創建一個帶事件的類:

public class EventDemo
{
    public delegate void EventDelegate(object sender, EventArgs e);

    public event EventDelegate DelegateEvent;


    public void Trigger()
    {
        if (DelegateEvent != null)// 觸發事件,按需判斷事件的訂閱者列表是否為空
        {
            DelegateEvent(this, new EventArgs());
        }
    }
}

使用一下:

EventDemo demo = new EventDemo(); 
demo.DelegateEvent += (sender, eventArgs) =>
{
    //省略訂閱者的方法內容
}
demo.Trigger();//觸發事件          

當發布者嘗試觸發事件的時候,訂閱者將會接收到消息,然後註冊訂閱者方法就會被調用。發布者向訂閱者傳遞一對sender和eventArgs,訂閱者按照自己的邏輯進行處理。

這裡很明顯可以看出,事件的處理程式註冊方法用的+=,所以與之對應的也有一個-=表示取消訂閱。

到這裡,委託和事件的基本概念就已經介紹完畢了,當然還是那句話,更多的內容在實踐中。C#的事件機制讓程式設計師有更多的自由去自定義事件,而不是被局限在某些框架內。所以大家可以多試試C#的事件,也許能發現更多的我不知道的內容呢。

更多內容煩請關注我的部落格

file