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

說說.net事件和委托

編輯:關於.NET

一說到.net的事件,也許你會想都說教程滿天飛,一個被說爛了的東西還有什麼可以說的啊?是啊,的確有很多好文章剖析事件,比如張子陽先生的C# 中的委托和事件

重溫Observer模式--熱水器·改 這兩篇文章讓我弄懂了委托、事件和觀察者模式的基礎知識,另外深入的事件文章還有博客堂 破寶的事件三部曲,(btw 這些都是我看過的,如果你見見過更好的文章請跟帖以便更多人學習,謝謝。:))

現在來說下這個被說爛了的東東我感覺需要注意的地方。

1 單播和多播事件

2 事件的顯式定義(繼而解釋委托和事件的區別)

3 .net事件模型

對於單播和多播事件概念。查資料是這麼定義的:單播事件就是對象(類)發出的事件通知,只能被外界的某一個事件處理程序處理,而不能被多個事件處理程序處理,多播事件是對象(類)發出的事件通知,可以同時被外界不同的事件處理程序處理。

說是這麼簡單,理解清楚不是很簡單。有沒有想過這裡是到底怎麼實現的呢?這裡我讀過《.net框架程序設計》第17.5 委托史話:System.Delegate與system.MulticastDelegate,如果有興趣可以找來看看,system.MulticastDelegate定義於FCL繼承自System.Delegate,這裡MulticastDelegate其實就是多播委托,那麼多播事件也是通過這個實現的,不用說Delegate大家都可以猜到是單播委托了,那麼平時我們定義一個委托public delegate void Back(Object value, Int32 item, Int32 numItems)

當編譯器遇到這句委托定義,會產生一個完整的類定義:

public class Back : System.MulticastDelegate
{
public Back(Object target, Int32 methodPtr);
public void virtual Invoke(Object value, Int32 item, Int32 numItems);
public virtual IAsyncResult BeginInvoke(Object vlaue ,Int32. numItems, AsyncCallback callback,Object object);
public virtual void EndInvoke(IAsyncResult result);
}

這個類其內部的方法看不懂沒關系,先看這個類的Back是繼承自System.MulticastDelegate,也就是說我們平時定義的委托幾乎都是繼承自多播委托的,那麼為什麼要有單播委托,這個具《.net框架程序設計》上說是微軟.net框架設計的一個缺陷。所以這裡大家記住平時定義的委托基本上都是多播的,也就是都可以用+=操作把委托組合成鏈,這裡我不能不說破寶對多播和單播的理解有誤。

事件的顯式定義,也許你還不知道顯式定義是怎麼回事,相信很多朋友平時自己定義事件也沒注意過這個問題。

回憶下平時我們是怎麼定義事件的呢?是不是下面的樣子:

classMailManager
{
//定義一個委托類
publicdelegatevoidMailMesgEventHandler(Objectsender,EventArgse);

//定義對應委托的事件
publiceventMailMesgEventHandlerMailMsg;
}

我們需要為事件先定義一個委托類(這裡EventArgs我省略沒自己定義特定子類),然後用這個委托類型定義事件。

看了很簡單,是的,這裡就是隱式定義事件,為什麼叫隱式呢,我自己弄的名字哈哈,編譯這句事件定義代碼時要產更多的代碼,就像下面這些簡化的偽碼:

private MailMesgEventHandler MailMesg = null;
public void add_MailMesg(MailMesgEventHandler handler)
{
MailMesg = (MailMesgEventHandler);
Delegate.Combine(MailMsg, handler);
}
public void remove_MailMesg(MailMesgEventHandler handler)
{
MailMesg = (MailMesgEventHandler);
Delegate.Remove(MailMsg, handler);
}
privateMailMesgEventHandlerMailMesg=null;
publicvoidadd_MailMesg(MailMesgEventHandlerhandler)
{
MailMesg=(MailMesgEventHandler);
Delegate.Combine(MailMsg,handler);
}
publicvoidremove_MailMesg(MailMesgEventHandlerhandler)
{
MailMesg=(MailMesgEventHandler);
Delegate.Remove(MailMsg,handler);
}

可以看到這裡編譯器產生了三塊東西,第一塊是定義一個委托字段,下面是對這個字段的add和remove訪問器,說到字段訪問器,你也許會說屬性!哈哈,是的,事件其實就是委托字段的訪問器,和屬性非常像,你可以這麼理解,事件就是委托的屬性,前面這句是從網上看到的,只不過屬性用的是get和set,事件是add和remove

待會我們談顯式定義事件的時候,你會更確定事件就是委托的屬性!

看來是時候說顯式定義事件啦,先看看下面的代碼:

public delegate void MailMesgEventHandler(Object sender, EventArgs e);
private MailMesgEventHandler MailMesg;
public event MailMesgeventHandler MailMsg
{
add
{
MailMesg = (MailMesgEventHandler);
Delegate.Combine(MailMsg, handler);
}
remove
{
MailMesg = (MailMesgEventHandler);
Delegate.Remove(MailMsg, handler);
}
}
publicdelegatevoidMailMesgEventHandler(Objectsender,EventArgse);
privateMailMesgEventHandlerMailMesg;
publiceventMailMesgeventHandlerMailMsg
{
add
{
MailMesg=(MailMesgEventHandler);
Delegate.Combine(MailMsg,handler);
}
remove
{
MailMesg=(MailMesgEventHandler);
Delegate.Remove(MailMsg,handler);
}
}

現在很像屬性了吧,和隱式生成代碼比較,這裡不同的是add和remover的寫法而已,那麼顯式定義的好處就很明顯了

我們可以控制add/remove內部的邏輯,這樣可以在+=(其實就是add,這裡是操作符重載)和-=(其實就是remove)時更靈活。

最後一個概念是.net事件模型,說事件模型之前先考慮一個問題,我們不管是隱式還是顯式定義事件最後都要定義一個委托字段,大家知道System.Windows.Forms.Control類型中大約60個事件,如果Control類型在實現這些事件的時候讓編譯器自動產生委托字段以及add和remove訪問器方法,那麼每個Control類型將僅僅因為事件就有將近60個委托,

由於我們大多數時候在對象上登記的事件都很少,因此沒創建一個Control類型(以及繼承自Control的類型)的實例都會很浪費內存,順便說下System.Web.UI.Control類型也存在這樣的問題。(以上斜體字是載自《.net框架程序設計修訂版》238-239頁)

那麼我們怎麼做能可以既定義事件又同時省去這些委托字段呢?

先看個圖:

看完了圖你是不是感覺如果用個表來存儲委托key/值,如果添加委托,首先查詢其中有沒有相應的關鍵字,沒有這時添加進去,有就合並到委托鏈。

這是個美妙的想法,同時可以各種委托有一個相同的“老窩”,而每個委托的工作又不相互干擾。

其實微軟就給我們提供了這樣的家,它就是System.ComponetModel.EventHandlerList

那麼至於裡面怎麼實現的我是不知道,不過現在我們可以自己動手做個委托“老窩”。(注下面代碼全部載自《.net框架程序設計修訂版,》)

 1using System;
2using System.Collections.Generic;
3using System.Text;
4using System.Collections;
5using System.Runtime.CompilerServices;
6
7namespace BaoBaoCore.Event
8{
9  /**//// <summary>
10  /// 事件集合類
11  /// 公用類似微軟System.ComponentModel.EventHandlerList
12  /// 速度比EventHandlerList快(因為其采用鏈表,而本類用的是hashtable)
13  /// </summary>
14  public class EventHandlerSet : IDisposable
15  {
16    //用於保存“事件鍵/委托值”對的私有散列表
17    private Hashtable _events = new Hashtable();
18
19    //一個索引器,用於獲取或設置與傳入的事件對象的
20    //散列鍵相關聯的委托
21    public virtual Delegate this[Object eventKey]
22    {
23      //如果對象不在集合中,則返回null
24      get
25      {
26        return (Delegate)_events[eventKey];
27      }
28
29      set
30      {
31        _events[eventKey] = value;
32      }
33    }
34
35    //在指定的事件對象的散列鍵對應的委托鏈表上添加/組合一個委托實例
36    public virtual void AddHandler(Object eventKey, Delegate handler)
37    {
38      _events[eventKey] = Delegate.Combine((Delegate)_events[eventKey], handler);
39    }
40
41    //在指定的事件對象的散列鍵對應的委托鏈表上移除一個委托實例
42    public virtual void RemoveHandler(Object eventKey, Delegate handler)
43    {
44      _events[eventKey] = Delegate.Remove((Delegate)_events[eventKey], handler);
45    }
46
47    //在指定的事件對象的散列鍵對應的委托鏈表上觸發事件
48    public virtual void Fire(Object eventKey, Object sender, EventArgs e)
49    {
50      Delegate d = (Delegate)_events[eventKey];
51      if (d != null)
52      {
53        d.DynamicInvoke(new Object[] { sender, e });
54      }
55    }
56
57    //方法聲明來源於IDisposable接口
58    //釋放對象以使散列表占用的內存資源在下一次垃圾
59    //收集中被回收,從而阻止垃圾收集器提升其代價
60    public void Dispose()
61    {
62      _events = null;
63    }
64
65    //下面的靜態方法返回一個對傳入的EventHandlerSet
66    //對象的線程安全的封裝
67    public static EventHandlerSet Synchronized(EventHandlerSet eventHandlerSet)
68    {
69      if (null == eventHandlerSet)
70      {
71        throw new ArgumentNullException("eventHandlerSet");
72      }
73      return new SynchronizedEventHandlerSet(eventHandlerSet);
74    }
75
76    //下面的類在EventHandlerSet基礎上提供了
77    //一個線程安全的封裝
78    private class SynchronizedEventHandlerSet : EventHandlerSet
79    {
80      //引用非線程安全的對象
81      private EventHandlerSet _eventHandlerSet;
82
83      //在非線程安全的對象上構造一個線程安全的封裝
84      public SynchronizedEventHandlerSet(EventHandlerSet eventHandlerSet)
85      {
86        this._eventHandlerSet = eventHandlerSet;
87
88        //釋放基類中的散列表對象
89        Dispose();
90      }
91
92      //線程安全的索引器
93      public override Delegate this[object eventKey]
94      {
95        [MethodImpl(MethodImplOptions.Synchronized)]
96        get
97        {
98          return _eventHandlerSet[eventKey];
99        }
100
101        [MethodImpl(MethodImplOptions.Synchronized)]
102        set
103        {
104          _eventHandlerSet[eventKey] = value;
105        }
106      }
107
108      //線程安全的AddHandler方法
109      [MethodImpl(MethodImplOptions.Synchronized)]
110      public override void AddHandler(object eventKey, Delegate handler)
111      {
112        _eventHandlerSet.AddHandler(eventKey, handler);
113      }
114
115      //線程安全的RemoveHandler方法
116      [MethodImpl(MethodImplOptions.Synchronized)]
117      public override void RemoveHandler(object eventKey, Delegate handler)
118      {
119        _eventHandlerSet.RemoveHandler(eventKey, handler);
120      }
121
122      //線程安全的Fire方法
123      [MethodImpl(MethodImplOptions.Synchronized)]
124      public override void Fire(object eventKey, object sender, EventArgs e)
125      {
126        _eventHandlerSet.Fire(eventKey, sender, e);
127      }
128    }
129  }
130}
131

以上是一個利用Hashtable存儲委托的實現類,下面我自己寫了個委托使用的具體案例,如下。

 1using System;
2using System.Collections.Generic;
3using System.ComponentModel;
4using System.Data;
5using System.Drawing;
6using System.Text;
7using System.Windows.Forms;
8
9using BaoBaoCore.Event;
10namespace WindowsSample
11{
12  public partial class Form1 : Form
13  {
14    //定義一個受保護的實例字段,改字段引用一個集合來管理一組事件/委托對。
15    private EventHandlerSet _workEventSet = EventHandlerSet.Synchronized(new EventHandlerSet());
16
17    //構造一些只讀且受保護的事件靜態key
18    protected static readonly Object WorkStartEventKey = new Object();
19    protected static readonly Object WorkEndEventKey = new Object();
20
21    //為WorkStart事件定義繼承自EventArgs的類型
22    public class WorkStartEventArgs : EventArgs
23    {
24      字段#region 字段
25      private DateTime _workStartTime;
26      private string _message = "";
27      #endregion
28
29      屬性#region 屬性
30      /**//// <summary>
31      /// 事件相關字串信息
32      /// </summary>
33      public string Message
34      {
35        get
36        {
37          return _message;
38        }
39      }
40
41      /**//// <summary>
42      /// 啟動時間
43      /// </summary>
44      public DateTime WorkStartTime
45      {
46        get
47        {
48          return _workStartTime;
49        }
50      }
51      #endregion
52
53      構造方法#region 構造方法
54      /**//// <summary>
55      /// 構造方法
56      /// </summary>
57      /// <param name="message">事件信息</param>
58      /// <param name="workStartTime">啟動時間</param>
59      public WorkStartEventArgs(string message, DateTime workStartTime)
60      {
61        _message = message;
62        _workStartTime = workStartTime;
63      }
64      #endregion
65    }
66
67    //為WorkEnd事件定義繼承自EventArgs的類型
68    public class WorkEndEventArgs : EventArgs
69    {
70      字段#region 字段
71      private DateTime _workEndTime;
72      private string _message = "";
73      #endregion
74
75      屬性#region 屬性
76      /**//// <summary>
77      /// 事件相關字串信息
78      /// </summary>
79      public string Message
80      {
81        get
82        {
83          return _message;
84        }
85      }
86
87      /**//// <summary>
88      /// 啟動時間
89      /// </summary>
90      public DateTime WorkEndTime
91      {
92        get
93        {
94          return _workEndTime;
95        }
96      }
97      #endregion
98
99      構造方法#region 構造方法
100      /**//// <summary>
101      /// 構造方法
102      /// </summary>
103      /// <param name="message">事件信息</param>
104      /// <param name="workStartTime">結束時間</param>
105      public WorkEndEventArgs(string message, DateTime workEndTime)
106      {
107        _message = message;
108        _workEndTime = workEndTime;
109      }
110      #endregion
111    }
112
113    //為事件定義委托原型
114    public delegate void WorkStartEventHandler(Object sender, WorkStartEventArgs e);
115    public delegate void WorkEndEventHandler(Object sender, WorkEndEventArgs e);
116
117    //定義WorkStart訪問器方法用於在集合上添加/移除委托實例
118    public event WorkStartEventHandler WorkStart
119    {
120      add
121      {
122        _workEventSet.AddHandler(WorkStartEventKey, value);
123      }
124
125      remove
126      {
127        _workEventSet.RemoveHandler(WorkStartEventKey, value);
128      }
129    }
130
131    //定義WorkEnd訪問器方法用於在集合上添加/移除委托實例
132    public event WorkEndEventHandler WorkEnd
133    {
134      add
135      {
136        _workEventSet.AddHandler(WorkEndEventKey, value);
137      }
138
139      remove
140      {
141        _workEventSet.RemoveHandler(WorkEndEventKey, value);
142      }
143    }
144
145    //為喚醒OnWorkStart事件定義一個受保護的虛方法
146    protected virtual void OnWorkStart(WorkStartEventArgs e)
147    {
148      _workEventSet.Fire(WorkStartEventKey, this, e);
149    }
150
151    //為喚醒OnWorkEnd事件定義一個受保護的虛方法
152    protected virtual void OnWorkEnd(WorkEndEventArgs e)
153    {
154      _workEventSet.Fire(WorkEndEventKey, this, e);
155    }
156
157    public Form1()
158    {
159      InitializeComponent();
160
161      //綁定自定義事件,通過結果可以看出先響應先添加的響應委托
162      this.WorkStart += new WorkStartEventHandler(WorkStart1);
163      this.WorkStart += new WorkStartEventHandler(WorkStart2);
164
165      this.WorkEnd += new WorkEndEventHandler(WorkEnd2);
166      this.WorkEnd += new WorkEndEventHandler(WorkEnd1);
167    }
168
169    private void button1_Click(object sender, EventArgs e)
170    {
171       OnWorkStart(new WorkStartEventArgs("工作開始啦!!", DateTime.Now));
172    }
173
174    private void button2_Click(object sender, EventArgs e)
175    {
176      OnWorkEnd(new WorkEndEventArgs("工作結束啦!!", DateTime.Now));
177    }
178
179    //事件相關響應方法
180    private void WorkStart1(object sender, WorkStartEventArgs e)
181    {
182      MessageBox.Show(e.Message + " 被WorkStart1方法捕獲到開始事件");
183    }
184
185    private void WorkStart2(object sender, WorkStartEventArgs e)
186    {
187      MessageBox.Show(e.WorkStartTime.ToString() + " 被WorkStart2方法捕獲到開始事件");
188    }
189
190    private void WorkEnd1(object sender, WorkEndEventArgs e)
191    {
192      MessageBox.Show(e.Message + " 被WorkEnd1方法捕獲到結束事件");
193    }
194
195    private void WorkEnd2(object sender, WorkEndEventArgs e)
196    {
197      MessageBox.Show(e.WorkEndTime.ToString() + " 被WorkEnd2方法捕獲到結束事件");
198    }
199
200   
201  }
202}

需要說明的是,EventHandlerSet只有在多處使用時才能體現出來其優勢,因為為每個事件定義關鍵字也需要內存,這裡我定義成靜態類的字段是有目的的,有興趣可以想想原因。

好了到這裡基本說完了,你看完了這篇事件的東東會不會再次說——都說破啦,哈哈。

出處:http://lovebaobao.cnblogs.com

本文配套源碼

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved