注:本文系學習筆記。
上一篇文章記錄了我對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的實現,我們可以將自定義事件的實現歸結為以下幾步: