原文: http://www.cnblogs.com/lovebaobao/archive/2008/12/17/1356769.html
一说到.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框架设计的一个缺陷。所以这里大家记住平时定义的委托基本上都是多播的,也就是都可以用+=操作把委托组合成链,这里我不能不说破宝对多播和单播的理解有误。
事件的显式定义,也许你还不知道显式定义是怎么回事,相信很多朋友平时自己定义事件也没注意过这个问题。
回忆下平时我们是怎么定义事件的呢?是不是下面的样子:
class MailManager
{
//定义一个委托类
public delegate void MailMesgEventHandler(Object sender, EventArgs e);
//定义对应委托的事件
public event MailMesgEventHandler MailMsg;
}
我们需要为事件先定义一个委托类(这里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);
}
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);
}
可以看到这里编译器产生了三块东西,第一块是定义一个委托字段,下面是对这个字段的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);
}
}
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);
}
}
现在很像属性了吧,和隐式生成代码比较,这里不同的是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框架程序设计修订版,》)
Code
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存储委托的实现类,下面我自己写了个委托使用的具体案例,如下。
Code
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只有在多处使用时才能体现出来其优势,因为为每个事件定义关键字也需要内存,这里我定义成静态类的字段是有目的的,有兴趣可以想想原因。
好了到这里基本说完了,你看完了这篇事件的东东会不会再次说——都说破啦,哈哈。