原文地址:http://blog.joycode.com/percyboy/archive/2005/01/22/43435.aspx
请注意,本文作者后来提到他自己犯了一个错误:
其实委托可以是多路的
这样的话,就没有必要分别存储多个委托,
而可以直接挂接在同一个委托实例上。
就是说,委托的一个实例可以同时挂接多个函数,
委托是具有 +=,-= 运算符的。
这一点,我写文章时不了解,给大家介绍的方法其实走了弯路。
属性样式的事件声明
在第一节中,我们讨论了 .NET 事件模型的基本实现方式。这一部分我们将学习 C# 语言提供的高级实现方式:使用 add/remove 访问器声明事件。(注:本节内容不适用于 VB.NET。)
我们再来看看上一节中我们声明事件的格式:
public event [委托类型] [事件名称];
这种声明方法,类似于类中的字段(field)。无论是否有事件处理程序挂接,它都会占用一定的内存空间。一般情况中,这样的内存消耗或许是微不足道的;然而,还是有些时候,内存开销会变得不可接受。比如,类似 System.Windows.Forms.Control 类型具有五六十个事件,这些事件并非每次都会挂接事件处理程序,如果每次都无端的多处这么多的内存开销,可能就无法容忍了。
好在 C# 语言提供了“属性”样式的事件声明方式:
public event [委托类型] [事件名称]
{
add {
. }
remove {
. }
}
如上的格式声明事件,具有 add 和 remove 访问器,看起来就像属性声明中的 get 和 set 访问器。使用特定的存储方式(比如使用 Hashtable 等集合结构),通过 add 和 remove 访问器,自定义你自己的事件处理程序添加和移除的实现方法。
Demo 1G:“属性”样式的事件声明。我首先给出一种实现方案如下(此实现参考了 .NET Framework SDK 文档中的一些提示)(限于篇幅,我只将主要的部分贴在这里):
public delegate void StartWorkEventHandler(object sender, StartWorkEventArgs e);
public delegate void RateReportEventHandler(object sender, RateReportEventArgs e);
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
// 注意:本例中的实现,仅支持“单播事件”。
// 如需要“多播事件”支持,请参考 Demo 1H 的实现。
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
// 为每种事件生成一个唯一的 object 作为键
static readonly object StartWorkEventKey = new object();
static readonly object EndWorkEventKey = new object();
static readonly object RateReportEventKey = new object();
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
// 使用 Hashtable 存储事件处理程序
private Hashtable handlers = new Hashtable();
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
// 使用 protected 方法而没有直接将 handlers.Add / handlers.Remove
// 写入事件 add / remove 访问器,是因为:
// 如果 Worker 具有子类的话,
// 我们不希望子类可以直接访问、修改 handlers 这个 Hashtable。
// 并且,子类如果有其他的事件定义,
// 也可以使用基类的这几个方法方便的增减事件处理程序。
protected void AddEventHandler(object eventKey, Delegate handler)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
{
lock(this)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
if (handlers[ eventKey ] == null)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
handlers.Add( eventKey, handler );
}
else
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
handlers[ eventKey ] = handler;
}
}
}
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
protected void RemoveEventHandler(object eventKey)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
{
lock(this)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
handlers.Remove( eventKey );
}
}
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
protected Delegate GetEventHandler(object eventKey)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
{
return (Delegate) handlers[ eventKey ];
}
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
// 使用了 add 和 remove 访问器的事件声明
public event StartWorkEventHandler StartWork
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
{
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
add
{ AddEventHandler(StartWorkEventKey, value); }
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
remove
{ RemoveEventHandler(StartWorkEventKey); }
}
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
public event EventHandler EndWork
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
{
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
add
{ AddEventHandler(EndWorkEventKey, value); }
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
remove
{ RemoveEventHandler(EndWorkEventKey); }
}
public event RateReportEventHandler RateReport
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
{
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
add
{ AddEventHandler(RateReportEventKey, value); }
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
remove
{ RemoveEventHandler(RateReportEventKey); }
}
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
// 此处需要做些相应调整
protected virtual void OnStartWork( StartWorkEventArgs e )
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
{
StartWorkEventHandler handler =
(StartWorkEventHandler) GetEventHandler( StartWorkEventKey );
if (handler != null)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
handler(this, e);
}
}
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
protected virtual void OnEndWork( EventArgs e )
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
{
EventHandler handler =
(EventHandler) GetEventHandler( EndWorkEventKey );
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
if (handler != null)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
handler(this, e);
}
}
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
protected virtual void OnRateReport( RateReportEventArgs e )
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
{
RateReportEventHandler handler =
(RateReportEventHandler) GetEventHandler( RateReportEventKey );
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
if (handler != null)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
handler(this, e);
}
}
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
public Worker()
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
{
}
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
public void DoLongTimeTask()
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
{
int i;
bool t = false;
double rate;
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
OnStartWork(new StartWorkEventArgs(MAX) );
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
for (i = 0; i <= MAX; i++)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
Thread.Sleep(1);
t = !t;
rate = (double)i / (double)MAX;
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
OnRateReport( new RateReportEventArgs(rate) );
}
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
OnEndWork( EventArgs.Empty );
}
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
细细研读这段代码,不难理解它的算法。这里,使用了名为 handlers 的 Hashtable 存储外部挂接上的事件处理程序。每当事件处理程序被“add”,就把它加入到 handlers 里存储;相反 remove 时,就将它从 handlers 里移除。这里取 event 的 key (开始部分为每一种 event 都生成了一个 object 作为代表这种 event 的 key)作为 Hashtable 的键。
单播事件和多播事件
在 Demo 1G 给出的解决方案中,你或许已经注意到:如果某一事件被挂接多次,则后挂接的事件处理程序,将改写先挂接的事件处理程序。这里就涉及到一个概念,叫“单播事件”。
所谓单播事件,就是对象(类)发出的事件通知,只能被外界的某一个事件处理程序处理,而不能被多个事件处理程序处理。也就是说,此事件只能被挂接一次,它只能“传播”到一个地方。相对的,就有“多播事件”,对象(类)发出的事件通知,可以同时被外界不同的事件处理程序处理。
打个比方,上一节开头时张三大叫一声之后,既招来了救护车,也招来了警察叔叔(问他是不是回不了家了),或许还有电视转播车(现场直播、采访张三为什么大叫,呵呵)。
多播事件会有很多特殊的用法。如果以后有机会向大家介绍 Observer 模式,可以看看 Observer 模式中是怎么运用多播事件的。(注:经我初步测试,字段形式的事件声明,默认是支持“多播事件”的。所以如果在事件种类不多时,我建议你采用上一节中所讲的字段形式的声明方式。)
支持多播事件的改进
Demo1H,支持多播事件。为了支持多播事件,我们需要改进存储结构,请参考下面的算法:
public delegate void StartWorkEventHandler(object sender, StartWorkEventArgs e);
public delegate void RateReportEventHandler(object sender, RateReportEventArgs e);
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
// 为每种事件生成一个唯一的键
static readonly object StartWorkEventKey = new object();
static readonly object EndWorkEventKey = new object();
static readonly object RateReportEventKey = new object();
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
// 为外部挂接的每一个事件处理程序,生成一个唯一的键
private object EventHandlerKey
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
{
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
get
{ return new object(); }
}
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
// 对比 Demo 1G,
// 为了支持“多播”,
// 这里使用两个 Hashtable:一个记录 handlers,
// 另一个记录这些 handler 分别对应的 event 类型(event 的类型用各自不同的 eventKey 来表示)。
// 两个 Hashtable 都使用 handlerKey 作为键。
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
// 使用 Hashtable 存储事件处理程序
private Hashtable handlers = new Hashtable();
// 另一个 Hashtable 存储这些 handler 对应的事件类型
private Hashtable events = new Hashtable();
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
protected void AddEventHandler(object eventKey, Delegate handler)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
{
// 注意添加时,首先取了一个 object 作为 handler 的 key,
// 并分别作为两个 Hashtable 的键。
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
lock(this)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
object handlerKey = EventHandlerKey;
handlers.Add( handlerKey, handler );
events.Add( handlerKey, eventKey);
}
}
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
protected void RemoveEventHandler(object eventKey, Delegate handler)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
{
// 移除时,遍历 events,对每一个符合 eventKey 的项,
// 分别检查其在 handlers 中的对应项,
// 如果两者都吻合,同时移除 events 和 handlers 中的对应项。
//
// 或许还有更简单的算法,不过我一时想不出来了 :(
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
lock(this)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
foreach ( object handlerKey in events.Keys)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
if (events[ handlerKey ] == eventKey)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
if ( (Delegate)handlers[ handlerKey ] == handler )
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
handlers.Remove( handlers[ handlerKey ] );
events.Remove( events[ handlerKey ] );
break;
}
}
}
}
}
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
protected ArrayList GetEventHandlers(object eventKey)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
{
ArrayList t = new ArrayList();
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
lock(this)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
foreach ( object handlerKey in events.Keys )
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
if ( events[ handlerKey ] == eventKey)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
t.Add( handlers[ handlerKey ] );
}
}
}
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
return t;
}
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
// 使用了 add 和 remove 访问器的事件声明
public event StartWorkEventHandler StartWork
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
{
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
add
{ AddEventHandler(StartWorkEventKey, value); }
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
remove
{ RemoveEventHandler(StartWorkEventKey, value); }
}
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
public event EventHandler EndWork
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
{
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
add
{ AddEventHandler(EndWorkEventKey, value); }
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
remove
{ RemoveEventHandler(EndWorkEventKey, value); }
}
public event RateReportEventHandler RateReport
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
{
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
add
{ AddEventHandler(RateReportEventKey, value); }
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
remove
{ RemoveEventHandler(RateReportEventKey, value); }
}
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
// 此处需要做些相应调整
protected virtual void OnStartWork( StartWorkEventArgs e )
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
{
ArrayList handlers = GetEventHandlers( StartWorkEventKey );
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
foreach(StartWorkEventHandler handler in handlers)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
handler(this, e);
}
}
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
protected virtual void OnEndWork( EventArgs e )
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
{
ArrayList handlers = GetEventHandlers( EndWorkEventKey );
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
foreach(EventHandler handler in handlers)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
handler(this, e);
}
}
![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
protected virtual void OnRateReport( RateReportEventArgs e )
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
{
ArrayList handlers = GetEventHandlers( RateReportEventKey );
![](https://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif)
foreach(RateReportEventHandler handler in handlers)
![](https://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
handler(this, e);
}
}
上面给出的算法,只是给你做参考,应该还有比这个实现更简单、更高效的方式。
为了实现“多播事件”,这次使用了两个 Hashtable:一个存储“handlerKey - handler”对,一个存储“handlerKey - eventKey”对。相信通过仔细研读,你可以读懂这段代码。我就不再赘述了。
下面为非原文内容,只是引用当时读者的评论:
1、对多播事件的一点意见。
看了一下对多播事件的处理方式,总体思路值得肯定,但在此处对用Hashtable来存储键值对觉得有些不妥。
一般按照传统采用非静态成员来标识事件类型的方式,当在客户端为一个事件预定多个事件处理函数的时候,是按照队列的方式来处理的(即先进先出原则)。
而在你的代码中采用了Hashtable就破坏了这个原则,因为对Hashtable的遍历并不是按照插入时的顺序进行的(见上面对events 的遍历)。所以我建议换成其它支持按插入时顺序进行遍历的集合类型,比如ListDictionary是个选择,不过当事件很多而对性能要求又很高时,需 考虑其它实现。(当然上面程序中的handlers仍然可以使用Hashtable)
2、 我的理解,未经证实:
挂接事件时将运算符由"+="改为"=",事件应该就由多播变成单播了。
也就是说.NET代理机制本身是以某种方式实现了一个队列。
3、自己不需要使用hashtable存储事件吧,.net类库的System.ComponentModel空间下有个保护EventHandlerList类。..........