注:本文系學習筆記。
上一篇文章記錄了我對C#中委托的理解。委托實際上是一種類型。可以將一個或多個方法綁定到委托上面,調用委托時,一次執行委托上面綁定的方法。本文要講述的事件實際上和委托有很深的“感情”。還是以上課的例子開始吧,假設距離上課時間前30分鐘去教室上課。在距離上課前5分鐘,會發生下面兩件事:預備上課鈴響,電子屏幕上顯示上課時間。我們以下面的代碼來表示模擬這個過程。
class Lesson{ private int remainTime;//距離上課時間 //課前動作 private void PrepareLesson(){ for(int i=30;i>=0;i--) { remainTime=i; if(i<=5) { RingBell(remainTime); DisplayLesson(remainTime); } } } //響鈴 private void RingBell(int remainTime){ console.WriteLine("叮鈴鈴,距離上課還有 {0} 分鐘,請同學們最好上課准備。",remainTime); } //屏幕顯示准備上課
private void DisplayLesson(int remainTime){ console.WriteLine("距離上課還有 {0} 分鐘。",remainTime); } } class Program{ static void main(){ Lesson lesson=new Lesson(); lesson.PrepareLesson(); } }
上面的代碼很清楚,能夠達到我們想要實現的效果。但是這樣寫並不好,假設學校期中考試期間,為了不打擾考試的考試,要求不能響鈴,而考試結束後恢復響鈴,這時候我們處理起來就比較麻煩。又或者我們的Lesson這個類表示課前准備工作,是表示上課前30分鐘,我們學生完成的一些事情(假設還有其他事情,比如復習上節課內容,預習新知識等等)。把響鈴和屏幕顯示上課時間放在這個類裡就會有點奇怪。根據面向對象原則,我們應該把響鈴和屏幕顯示單獨放在各自的一個類裡。代碼修改如下:
public class Lesson{ private int remainTime; private void PrepareLesson(){ for(int i=30;i>=0;i--) { remainTime=i; } } } public class Bell{ private void RingBell(int remainTime){
console.WriteLine("叮鈴鈴,距離上課還有 {0} 分鐘,請同學們最好上課准備。",remainTime);
}
}
public class Display{
private void DisplayLesson(int remainTime){
console.WriteLine("距離上課還有 {0} 分鐘。",remainTime);
}
}
這樣就可以了,但是現在,如何讓在距離上課時間不到5分鐘的時候,響鈴和屏幕顯示准備上課呢。這裡用到ObServer設計模式。這裡簡單舉個例子說明ObServer設計模式,中國移動有提供每月話費賬單、流量賬單之類的查詢業務。但是並不是每個人都需要它推送這樣的消息。有的人可能不需要查詢,有的人可能只關心話費賬單,有的人可能只關心流量問題,有的人可能兩者都需要。那麼移動公司具體是如何為每個人提供他所需要的服務呢?當然是根據用戶訂閱的種類,用戶關心的什麼,就發送什麼。Observer設計模式與此類似,它包含兩類對象。
Observer設計模式:Observer設計模式是為了定義對象間的一種一對多的依賴關系,以便於當一個對象的狀態改變時,其他依賴於它的對象會被自動告知並更新。Observer模式是一種松耦合的設計模式。
下面繼續修改代碼
上例可見,事件實際上就是一個委托。
public class Lesson{ private int remainTime; public delegate void PrepareHandler(int remainTime); public event PrepareHandler PrepareEvent; private void PrepareLesson(){ for(int i=30;i>=0;i--) { remainTime=i; if(i<=5) { if(PrepareEvent!=null) { PrepareEvent(remainTime); } } } } } public class Bell{ private void RingBell(int remainTime){ console.WriteLine("叮鈴鈴,距離上課還有 {0} 分鐘,請同學們最好上課准備。",remainTime); } } public class Display{ private static void DisplayLesson(int remainTime){ console.WriteLine("距離上課還有 {0} 分鐘。",remainTime); } } class Program{ static void main(){ Lesson lesson=new Lesson(); lesson.PrepareEvent+=(new Bell()).RingBell; lesson.PrepareEvent+=Display.DisplayLesson; lesson.PrepareLesson(); } }
那麼事件跟委托有什麼區別呢,上篇文章介紹了,委托必須初始化之後才能添加綁定的方法,而上面的代碼我們可以看到直接給事件添加綁定方法。這是因為事件是一個封裝了的委托,.NET框架實際上在編譯的時候已經為時間做了初始化。上面事件的用法與我們見到的.NET中的事件形式上不同,實際上.NET Framework中的事件模型是規范化了的,.NET事件的編碼規范如下
那麼我們繼續修改我們的代碼,讓它遵循規范
class Lesson{ private int remainTime; public delegate void PrepareEventHandler(Object sender,PrepareEventArgs e); public Event PrepareEventHandler Prepare; public class PrepareEventArgs:EventArgs{ public readonly int remainTime; public PrepareEventArgs(int remainTime){ this.remainTime=remainTime; } } protected virtual void OnPrepare(PrepareEventArgs e){ if (Prepare!=null) { Prepare(this,e) } } public void PrepareLesson(){ for(int i=30;i>=0;i--) { remainTime=i; if(remainTime<=5) { PrepareEventArgs e=new PrepareEventArgs(remainTime); OnPrepare(e) } } } public class Bell{ public void RingBell(Object sender,Lesson.PrepareEventArgs e){ Lesson lesson=(Lesson)sender; console.WriteLine("叮鈴鈴,距離上課還有 {0} 分鐘,請同學們最好上課准備。",e.remainTime); } } public class Display{ public static void DisplayLesson(Object sender,Lesson.PrepareEventArgs e){ Lesson lesson=(Lesson)sender; console.WriteLine("距離上課還有 {0} 分鐘。",e.remainTime); } } class Program{ static void main(){ Lesson lesson=new Lesson(); Bell bell=new Bell(); lesson.Prepare+=bell.RingBell; lesson.Prepare+=Display.DiaplayLesson; lesson.PrepareLesson(); } }
最後總結一下:C#中的事件處理實際上是一種具有特殊簽名的delegate,它是將委托進行封裝,不允許直接方位委托本身,只能通過給委托添加和移除綁定的方法。(+=、-=實際上是調用了add 和 remove方法)像下面這個樣子:
public delegate void MyEventHandler(object sender, MyEventArgs e);
其中的兩個參數,sender代表事件發送者,e是事件參數類。MyEventArgs類用來包含與事件相關的數據,所有的事件參數類都必須從System.EventArgs類派生。當然,如果你的事件不含參數,那麼可以直接用System.EventArgs類作為參數。
就是這麼簡單,結合delegate的實現,我們可以將自定義事件的實現歸結為以下幾步: