iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

2
Software Development

我要轉職成 C# / .NET 工程師系列 第 34

C# 事件(下) – 加上event關鍵字

上篇文章中我們使用委派實作了事件處理機制,因為使用委派來實作非常方便,可以幫我們接受多筆客戶訂閱又能集結成冊,而執行委派又能實踐通知訂閱者者,一次幫助我們解決許多問題。

但是委派要能讓外部客戶訂閱,就要能被外部使用,所以委派也有可能被外部執行而送出意料之外的通知。
https://ithelp.ithome.com.tw/upload/images/20191103/20120420U9KFMyuwpd.png

使用event關鍵字

而這個問題並不是private、protected這類存取修飾字可以解決的,所以在C#中,提供了envent關鍵字來解決這個問題,讓委派能被外部加入方法,又能禁止被外部執行。

用法很簡單,只要在之前的程式碼上,在使用我們自定義的委派型別前加上event就可以了,程式碼如下:

class 報社
{
	public delegate void 通知對象(string 新聞報導);

	public event 通知對象 最新新聞; //只有這一行有更改

	public void 投稿新聞(string 新聞稿)
	{
		最新新聞.Invoke(新聞稿);
	}
}

加上event後,C#編譯器就會禁止外部來執行這個委派。
https://ithelp.ithome.com.tw/upload/images/20191103/201204200aLeh1jcaY.png

觸發事件時~

當要執行委派,要記的檢查有沒有人訂閱(新聞事件),若沒人訂閱委派,我們的訂閱名冊就會是null,執行時就會出現例外Null Reference Exception
https://ithelp.ithome.com.tw/upload/images/20191103/20120420qLxbLjWJH0.png

若者個委派可能在內部多個地方都有可能觸發到,那可以包裝成一個方法,在C#與.NET中似乎習慣將觸發事件方法前面加上On,程式碼如下:
https://ithelp.ithome.com.tw/upload/images/20191103/20120420t4u85BiugL.png

C# 6.0可以使用?來簡化,是否為null的判斷:

最新新聞?.Invoke(新聞);

C#編譯器也會為我們提示
https://ithelp.ithome.com.tw/upload/images/20191103/201204205AiSItc3vW.png

一些小改善

在以上範例能看到使用者訂閱新聞的過程,想當然地,一個使用者也能訂閱多個新聞,所以觸發事件的委派本身,要能讓訂閱者知道這是哪一間報社通知的新聞,所以.NET中會把委派本身當成參數傳給訂閱者的通知方法,讓使用者知道這次的通知是由哪個報社派送的。

public  delegate void 通知對象(報社 有間報社 ,string 新聞報導);

另外通知方法能接收的參數有多有少,每個委派的設定略有不同,我們可以將參數集合起來包裝成一個類別來傳遞。這樣以後要傳遞的參數需要修改或是數量增減也比較方便調整。

public class 新聞
{
	public string 標題;
	public string 內容;
}

如此一來,委派型別就可以精簡成只要兩個參數就好。

public  delegate void 通知對象(報社 有間報社 ,新聞 新聞報導);

.NET中事件處理的慣例

上一節一些小改善可能會讓人納悶是否真的要如此,其實只是為了這一節做鋪陳。事件用的委派型別既然只要兩個參數就好的話,.NET類別庫本身也提供了現成的已經定義好的委派

public delegate void EventHandler(object sender, EventArgs eventArgs);

sender就是來源物件,型別是最廣泛的object,而傳遞參數集合宣告的型別是.NET類別庫定義好的EventArgs。所以要使用這個定義好的委派EventHandler,就要將我們定義好的新聞類別繼承自EventArgs。
https://ithelp.ithome.com.tw/upload/images/20191103/20120420LN8tQLZaOQ.png

但是訂閱者的通知方法可能要自己轉型,最後發行類別與訂閱類別就可以寫成如下:

       class 報社
        {
            public string 名稱;

            public event  EventHandler 最新新聞;

            public void 投稿新聞(string 訊息)
            {
                新聞 new新聞 = new 新聞() { 標題 = "最新快訊", 內容 = 訊息 };
                On收到最新新聞時(this, new新聞);
            }

            protected void On收到最新新聞時(報社 報社, 新聞 新聞)
            {
                最新新聞?.Invoke(報社, 新聞);
            }
        }


        class 訂閱者
        {
            public string 名字 ;
            public void 通知我(object sender , EventArgs eventArgs)
            {
				報社 報社 = sender as 報社;
                新聞 新聞 = eventArgs as 新聞;
                Console.WriteLine($"我是{名字},我已經收到來自{來源.名稱}的{新聞.標題}:{新聞.內容}");
            }
        }

執行結果:
https://ithelp.ithome.com.tw/upload/images/20191103/20120420a7GnMDOLui.png
主程式:
https://ithelp.ithome.com.tw/upload/images/20191103/20120420cY4tAL7mfb.png

事件是一種特別的委派

也可以說委派是事件的基礎,就算使用.NET提供的EventHandler,但背後實作還是依靠委派delegate。比較不同的地方在於,使用event關鍵字來限制委派的部分功能,讓委派不會被外部執行,但本質上還是委派,只是多了一道鎖。

另外事件的目的也只是單向傳遞,所以委派接受的方法並不會有回傳值,就像郵差派送出去也是送後不理,要回信要自己另外寄。當然一方面的原因,也是因為委派本來就不能接收多個方法的回傳值,只能接受一個方法的回傳。

這是我的FB粉專,歡迎大家來按讚:長庚的作業簿
還有我的部落格:https://dannyliu.me

本次內容參考自《C#本事》


上一篇
C# 事件(上) - 使用委派來實作事件
系列文
我要轉職成 C# / .NET 工程師34

尚未有邦友留言

立即登入留言