再談C#中的委託和事件

再談C#中的委託和事件

寫在最前

我相信全網關於委託和事件的文章和概述,大家應該已經讀過很多篇。但是就我的觀察來看,大多數文在講述這方面概念時,都會用燒開水和狗叫主人的例子來講述事件怎麼工作,這樣比喻固然與生活聯絡緊密,但看多了難免有一些審美疲勞。

所以今天,我打算結合自己的一些工作經歷,再來談談我個人對委託和事件的理解,希望能帶給大家一些不一樣的見解。

先談概念

委託:一種引用型別,表示

具有特定引數列表和返回型別的

方法的引用

。在例項化委託時,你可以將其例項與任何具有相容簽名和返回型別的方法相關聯。你可以透過委託例項呼叫方法。委託用於將方法作為引數傳遞給其他方法。事件處理程式就是透過委託呼叫的方法。

事件:類或物件可以

透過事件向其他類或物件通知發生的相關事情

。傳送(或引發)事件的類稱為“釋出者”,接收(或處理)事件的類稱為“訂閱者”。

以上概述來自MSDN官方文件:

https://docs。microsoft。com/zh-cn/dotnet/csharp/programming-guide/delegates/

從概念中我們其實已經可以看出,委託主要是對方法的一種引用,而事件則充當了多個類或者物件進行相互通知的橋樑。

如果我這麼解釋你可以明白的話,那麼我們今天的主題就已經明朗了,下面我們就用具體的程式碼例項來講述。

再看例子

委託

我們需要先宣告一個委託例項,在C#中,顯示宣告自定義委託採用

delegate

關鍵字,宣告方式與宣告普通方法相同,需要指定訪問範圍和返回型別,同時包含訪問引數。

同時我們針對委託,宣告對應的方法,方法的返回值和引數需要與委託保持一致,若不一致則會在委託傳遞方法時出現編譯錯誤。

委託執行內部傳遞方法的方式是使用Invoke方法,此處需注意,C#中同時提供了BeginInvoke和EndInvoke的方法對,用於非同步執行內部的方法,具體含義和用法可參考我之前的一篇文章:

下面我們一起來看一下示例:

在此示例中,我們分別定義了一個無引數無返回值的委託和一個包含2個引數並返回int型別的委託,分別用於執行兩種對應的方法。在兩個委託執行對應的Invoke方法之後,會產生以下的結果:

再談C#中的委託和事件

結果和我們預期一致,程式同步順序地執行了兩個委託並打印出相應的結果。但是看到這裡也許你會有一個疑問,既然委託執行時的結果與直接呼叫方法一致,那麼我們為什麼還需要使用委託來執行方法呢?

這時我們就要回到最初的定義:

委託用於將方法作為引數傳遞給其他方法

由於例項化的委託是一個物件,因此可以作為引數傳遞或分配給一個屬性。這允許方法接受委託作為引數並在稍後呼叫委託。這被稱為非同步回撥,是在長程序完成時通知呼叫方的常用方法。當以這種方式使用委託時,使用委託的程式碼不需要知道要使用的實現方法。功能類似於封裝介面提供的功能。

我們一起使用一個比較直觀的例子來驗證:

在這段程式碼中,我們聲明瞭一個無返回值委託Del,用於接收傳入的訊息,並且該委託指向了一個呼叫控制檯的方法DelegateMethod。而後續我們呼叫MethodWithCallback方法時,無需呼叫控制檯相關方法,而是直接將Del委託的例項作為引數傳入,就實現DelegateMethod方法的呼叫。這個例項就是我們上述提到的非同步回撥和委託對方法的引用。

執行結果如下:

再談C#中的委託和事件

此處我使用了JetBrains出品的IDE Rider,因此截圖介面會與VS有所不同,喜歡輕量級IDE的同學可以試試這款,有30天的免費試用期,地址:Rider: The Cross-Platform 。NET IDE from JetBrains,此處不再過多講解。

根據我們上述的例項講解,大家對委託的作用及使用場景應該有了初步的理解,但是我們仔細想一想,上述的場景似乎少了些什麼,為什麼我們的委託始終只能指向一個方法進行呼叫呢?

這裡就要引出我們接下來的概念:多播委託。

事實上,委託是可以呼叫多個方法的,這種方式就叫做多播委託,在C#中,我們可以使用+=的運算子,將其他委託附加到當前委託之後,就可以實現多播委託,相關示例如下:

在這個示例中,我們重新編寫了一個方法叫DelegateMethod2,同時我們又聲明瞭一個新的委託物件handlerNew指向該方法。接著我們使用+=的方式將handlerNew新增至handler並執行該委託,得到的結果如下:

如我們先前所料,多播委託把多個方法依次進行了執行。此時如果某個方法發生異常,則不會呼叫列表中的後續方法,如果委託具有返回值和/或輸出引數,它將返回上次呼叫方法的返回值和引數。與增加方法相對應,若要刪除呼叫列表的方法,則可以使用-=運算子進行操作。

關於委託的理解與常用方式,我們就講解到這裡,事實上,多播委託常用於事件處理中,由此可見,事件與委託有著千絲萬縷的聯絡,下面我們就拉開事件的序幕。

事件

如前文講解時所說,事件是一種通知行為,因此要分為事件釋出者和事件訂閱者。而且在。Net中,事件基於EventHandler委託和EventArgs基類的,因此我們在宣告事件時,需要先定義一個委託型別,然後使用event關鍵字進行事件的定義。

相關的示例如下:

從事例中我們可以看出,我們分別定義了釋出者和訂閱者相關的類。

在釋出者中,我們需要宣告一個委託NoticeHandler,然後定義一個此型別的事件NoticeEvent。在定義物件之後,我們需要對事件進行執行,因此有了OnNoticed方法,此方法只用於事件本身的執行。那麼什麼時候才能執行事件呢?於是我們又有了觸發該事件的方法Works,當Works方法被呼叫時,就會觸發NoticeEvent事件。

而在訂閱者中,我們需要對NoticeEvent事件進行訂閱,因此我們需要釋出者的物件PublishEvent,同時需要對它的事件進行訂閱。正如我們前文所說,訂閱使用+=的方式,與多播委託的使用是一致的,而+=後的物件正是我們需要響應後續處理的方法PrintResult。當事件被觸發時,訂閱者會接收到該事件,並自動執行響應函式PrintResult。

執行結果如下圖所示:

從執行結果中我們可以看出,在事件被觸發後,訂閱者成功接收到了釋出者釋出的事件內容,並進行自動響應,而我們在此過程中從未顯式呼叫訂閱者的任何方法,這也是事件模型的本質意義:從釋出到訂閱。

在微軟官方文件中提到,事件是一種特殊的多播委託,只能從宣告它的類中進行呼叫。客戶端程式碼透過提供對應在引發事件時呼叫的方法的引用來訂閱事件。這些方法透過事件訪問器新增到委託的呼叫列表中,這也是我們可以使用+=去訂閱事件的原因所在,而取消事件則和多播委託一致,使用-=的方式。

關於事件的使用場景還有一種與多執行緒通知相關的典型用法,具體含義和用法可參考我之前的另一篇文章:

最後總結

本文我們講解了委託和事件之間的關係,以及它們的使用場景。在我個人的工作經歷中,曾經有3年左右的時間從事C/S相關的開發工作,其中包含了大量多執行緒、委託和事件的使用場景。主要用於在開發WinForm程式時,不同窗體(包含父子窗體)之間進行相互通訊,其中都是基於事件的釋出和訂閱作為實現的。而委託的使用場景則更多,很多C#方法在使用時都會傳入一個Action或者Func委託作為引數,而這些引數同時又支援Lambda表示式,這就又引出了匿名函式的概念,由於篇幅所限,此處不再進行講解,大家可以自行搜尋資料進行了解和學習。

除了具體的技術點之外,在我們的設計模式中也有事件的使用身影,最典型的莫過於

觀察者模式

。關於觀察者模式,網上眾說紛紜,也有很多資料會將它與釋出-訂閱模式混為一談。而實際上這兩種模式並不完全是同一種概念和實現方式,那麼下一次我們將會從設計模式著手,談一談觀察者模式和釋出-訂閱模式的異同,敬請期待!

技術群:新增小編微信並備註進群

頂部