程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#基礎知識 >> 學習和理解C#中的事件

學習和理解C#中的事件

編輯:C#基礎知識

  注:本文系學習筆記。

  上一篇文章記錄了我對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設計模式與此類似,它包含兩類對象。

  1. Subject:監視對象,它包含著其他對象所感興趣的內容。在本范例中,上課就是一個監視對象,它包含的其他對象所感興趣的內容,就是remainTime字段,當這個字段的值小於等於5時,會不斷把數據發給監視它的對象。
  2. Observer:監視者,它監視Subject,當Subject中的某件事發生的時候,會告知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事件的編碼規范如下

  • 委托類型的名稱都應該以EventHandler結束。
  • 委托的原型定義:有一個void返回值,並接受兩個輸入參數:一個Object 類型,一個 EventArgs類型(或繼承自EventArgs)。
  • 事件的命名為 委托去掉 EventHandler之後剩余的部分。
  • 繼承自EventArgs的類型應該以EventArgs結尾。

     那麼我們繼續修改我們的代碼,讓它遵循規范

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的實現,我們可以將自定義事件的實現歸結為以下幾步:

  • 定義delegate對象類型,它有兩個參數,第一個參數是事件發送者對象,第二個參數是事件參數類對象。
  • 定義事件參數類,此類應當從System.EventArgs類派生。如果事件不帶參數,這一步可以省略。
  • 定義事件處理方法,它應當與delegate對象具有相同的參數和返回值類型。
  • 用event關鍵字定義事件對象,它同時也是一個delegate對象。
  • 用+=操作符添加事件到事件隊列中(-=操作符能夠將事件從隊列中刪除)。
  • 需要觸發事件的地方用調用delegate的方式寫事件觸發方法。一般來說,此方法應為protected訪問限制,既不能以public方式調用,但可以被子類繼承。名字是OnEventName。
  • 適當的地方調用事件觸發方法觸發事件。
  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved