也談事件(Event)

作者: Artech  來源: 博客園  發布時間: 2010-07-21 20:34  閱讀: 471 次  推薦: 0   原文鏈接   [收藏]  

最近園子里發表了一些討論“事件(Event)”的文章,我也來湊個熱鬧,談談我對事件的一些粗淺的認識。本文不談設計模式(觀察者模式),只從運行時的角度來分析事件這個對象到底是個什么東西,它有那么神秘嗎?為了更好的分析事件,本文將會編寫一些例子來模擬事件的訂閱機制。本文對事件的分析可以概括為下面三句話:

一、Delegate = Object + MethodInfo

其實你完全可以通過Reflector這樣的工具來看Delegate類型是如何定義的。在這里,我們只關注Delegate本質的東西,即Delegate最終是如果執行的。為此,我創建了下面一個簡單的MyDelegate類型來模擬Delegate

   1: public class MyDelegate
   2: {           
   3:  
   4:     public object Target { get; private set; }
   5:     public MethodInfo Method { get; private set; }       
   6:  
   7:     public MyDelegate(object target, MethodInfo method)
   8:     {
   9:         this.Target = target;
  10:         this.Method = method;
  11:     }      
  12:  
  13:     public virtual void Invoke(params object[] args)
  14:     {
  15:         this.Method.Invoke(this.Target, args);           
  16:     }      
  17: }

從上面的定義可以看到,MyDelegate只有兩個屬性:Object類型的Target和MethodInfo類型的Method。委托的執行通過需方法Invoke完成,具體來說,最終的執行通過反射的方式調用Method的Invoke方法完成。

二、MulticastDelegate對象多個Delegate對象的鏈表

其實我們平時講的委托,并不是一個單個的Delegate對象,實際上是一個委托鏈,這樣一個委托鏈通過MulticastDelegate定義。由于定義也相對復雜,我們同樣通過定義模擬類型來反映其本質的東西。為此,我創建了如下一個MyMulticastDelegate類型。

   1: public class MyMulticastDelegate : MyDelegate
   2: {
   3:     public MyMulticastDelegate Next { get; set; }
   4:  
   5:     public MyMulticastDelegate(object target, MethodInfo method)
   6:         : base(target, method)
   7:     { }
   8:  
   9:     public override void Invoke(params object[] args)
  10:     {
  11:         base.Invoke(args);
  12:         if (null != Next)
  13:         {
  14:             this.Next.Invoke(args);
  15:         }
  16:     }
  17: }

MyMulticastDelegate繼承自上面定義的MyDelegate類型,在此基礎上定義了一個額外的屬性Next,代表委托鏈中當前委托對象的下一個委托。最后,Invoke方法被重寫:按照委托鏈的順序依次執行每一個委托對象。

三、事件本質上是一個MulticastDelegate對象

我們使用的事件一般通過EventHandler或者System.EventHandler表示,其本質來時一個通過MulticastDelegate對象表示的委托鏈。事件注冊本質就是將另外一個委托(鏈)連到當前委托鏈上。下面定義的類型MyEventHandler模擬了事件的實現。

   1: public class MyEventHandler : MyMulticastDelegate
   2: {
   3:     public MyEventHandler(object target, MethodInfo method)
   4:         : base(target, method)
   5:     { }        
   6:  
   7:     public void Fire(object sender,EventArgs args)
   8:     {
   9:         this.Invoke(sender,args);
  10:     }
  11:  
  12:     public static MyEventHandler operator +(MyEventHandler current, MyEventHandler next)
  13:     {
  14:         if (null == current)
  15:         {
  16:             return next;
  17:         }
  18:  
  19:         MyMulticastDelegate terminator = current;
  20:         while (null != terminator.Next)
  21:         {
  22:             terminator = terminator.Next;
  23:         }
  24:  
  25:         terminator.Next = next;
  26:         return current;
  27:     }
  28:  
  29:     public static implicit operator MyEventHandler(EventHandler eventHandler)
  30:     {
  31:         return new MyEventHandler(eventHandler.Target, eventHandler.Method);
  32:     }
  33: }

事件一般通過+=操作符進行注冊,其本質就是將兩個委托鏈相連。為此,在MyEventHandler中,我也重載了操作符+。事件的觸發被定義在Fire方法中,其實現就是調用MyMulticastDelegate的Invoke方法。此外,我還定義一個隱式類型轉換操作符,將EventHandler轉對象化成MyEventHandler類型。

四、事件的訂閱

現在我通過一個具體的例子來說明通過上面定義的MyEventHandler來模擬具體的事件注冊和觸發。在這里,我們模擬的是Button的Click事件,為此我采用標準的事件編程方式定義了如下一個Button類型。MyEventHandler類型的Click屬性代表事件本身,Click操作的觸發通過執行PerformClick方法完成。進一步地,Click操作的處理實現在虛方法OnClick中,其本質就是調用MyEventHandler的Fire方法。

   1: public class Button
   2: {
   3:     public string Id { get; private set; }
   4:     public MyEventHandler Click { get; set; }
   5:  
   6:     public Button(string id)
   7:     {
   8:         this.Id = id;
   9:     }
  10:  
  11:     protected virtual void OnClick(EventArgs args)
  12:     {
  13:         if(null != this.Click)
  14:         {
  15:             this.Click.Fire(this, args);
  16:         }
  17:     }
  18:  
  19:     public void PerformClick()
  20:     {
  21:         this.OnClick(EventArgs.Empty);
  22:     }
  23: } 

接下來,我們創建另一個模擬訂閱Button對象的Click事件的類型,這樣一個簡單的類型Foo定義如下。當訂閱的Click事件觸發之后,會回調DoSomethingOnceClick方法,方法會在控制臺上輸出一段文字。

   1: public class Foo
   2: {
   3:     public void DoSomethingOnceClick(object sender, EventArgs args)
   4:     { 
   5:         Button btn = sender as Button;
   6:         if(null != btn)
   7:         {
   8:             Console.WriteLine("Click {0}", btn.Id);
   9:         }
  10:     }
  11: }

那么最終的事件訂閱和觸發編寫在下面代碼中:在創建的Button對象中,進行了6次相同的事件注冊,最終通過PerformClick方法觸發事件。由于在MyEventHandler定義一個從EventHandler到MyEventHandler類型的隱式轉換操作符,所以我們進行事件注冊和傳統的方式別無二致。

   1: class Program
   2: {
   3:     static void Main(string[] args)
   4:     {
   5:         var btn1 = new Button("Button1");
   6:         var foo = new Foo();
   7:         btn1.Click += new EventHandler(foo.DoSomethingOnceClick);
   8:         btn1.Click += new EventHandler(foo.DoSomethingOnceClick);
   9:         btn1.Click += new EventHandler(foo.DoSomethingOnceClick);
  10:         btn1.Click += new EventHandler(foo.DoSomethingOnceClick);
  11:         btn1.Click += new EventHandler(foo.DoSomethingOnceClick);
  12:         btn1.Click += new EventHandler(foo.DoSomethingOnceClick);
  13:         btn1.PerformClick();
  14:     } 
  15: }

下面是最后的輸出結果:

Click Button1
Click Button1
Click Button1
Click Button1
Click Button1
Click Button1

本文提供的例子,你可以通過這里下載,關于事件相關的內容,我還有一篇相關的文章《如何編寫沒有Try/Catch的程序》,僅供參考。

作者:Artech
出處:http://artech.cnblogs.com
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。
0
0
 
 
 
 

文章列表

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()