破宝

我是一块破破烂烂的宝贝石头。
随笔 - 94, 评论 - 1281, 引用 - 52

导航

关于

自选精华版 RECOMMENDATIONS
留言板 GUESTBOOK

本人 blog 文章、图片及其他资源等,除另有声明外,均遵循以下原则向全球(当然包括朝鲜、古巴、利比亚等国)共享:

1。欢迎转载、复制、传播、引用,但转载、复制(包括但不仅限于作为参考资料复制到本地)、传播、引用同时必须在显著位置注明作者(破宝/percyboy)和文章原始 URL 地址等信息。但商业转载、复制、传播(尤指用于图书、光盘等媒体的部分或全部),须事先征得本人的许可。

2。文章以“现状”提供,不为由于使用本站资源而造成的任何损失而负责,仅提供力所能及的咨询和参考意见。

3。关于修改:允许您将本 blog 中的资源作为参考资料复制时的一定修改,但仍须保留作者和出处信息;其他情况下的修改(包括修改后再发布),须和本人确认许可。
 

标签

每月存档

最新留言

广告

 

目录

属性样式的事件声明

在第一节中,我们讨论了 .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);

        // 注意:本例中的实现,仅支持“单播事件”。
        // 如需要“多播事件”支持,请参考 Demo 1H 的实现。

        // 为每种事件生成一个唯一的 object 作为键
        static readonly object StartWorkEventKey = new object();
        static readonly object EndWorkEventKey = new object();
        static readonly object RateReportEventKey = new object();

        // 使用 Hashtable 存储事件处理程序
        private Hashtable handlers = new Hashtable();

        // 使用 protected 方法而没有直接将 handlers.Add / handlers.Remove
        // 写入事件 add / remove 访问器,是因为:
        // 如果 Worker 具有子类的话,
        // 我们不希望子类可以直接访问、修改 handlers 这个 Hashtable。
        // 并且,子类如果有其他的事件定义,
        // 也可以使用基类的这几个方法方便的增减事件处理程序。
        protected void AddEventHandler(object eventKey, Delegate handler)
        {
            lock(this)
            {
                if (handlers[ eventKey ] == null)
                {
                    handlers.Add( eventKey, handler );
                }
                else
                {
                    handlers[ eventKey ] = handler;
                }
            }
        }

        protected void RemoveEventHandler(object eventKey)
        {
            lock(this)
            {
                handlers.Remove( eventKey );
            }
        }

        protected Delegate GetEventHandler(object eventKey)
        {
            return (Delegate) handlers[ eventKey ];
        }

        // 使用了 add 和 remove 访问器的事件声明
        public event StartWorkEventHandler StartWork
        {
            add { AddEventHandler(StartWorkEventKey, value); }
            remove { RemoveEventHandler(StartWorkEventKey); }
        }

        public event EventHandler EndWork
        {
            add { AddEventHandler(EndWorkEventKey, value); }
            remove { RemoveEventHandler(EndWorkEventKey); }
        }
        
        public event RateReportEventHandler RateReport
        {
            add { AddEventHandler(RateReportEventKey, value); }
            remove { RemoveEventHandler(RateReportEventKey); }
        }

        // 此处需要做些相应调整
        protected virtual void OnStartWork( StartWorkEventArgs e )
        {
            StartWorkEventHandler handler = 
                (StartWorkEventHandler) GetEventHandler( StartWorkEventKey );
            if (handler != null)
            {
                handler(this, e);
            }
        }

        protected virtual void OnEndWork( EventArgs e )
        {
            EventHandler handler =
                (EventHandler) GetEventHandler( EndWorkEventKey );

            if (handler != null)
            {
                handler(this, e);
            }
        }

        protected virtual void OnRateReport( RateReportEventArgs e )
        {
            RateReportEventHandler handler =
                (RateReportEventHandler) GetEventHandler( RateReportEventKey );

            if (handler != null)
            {
                handler(this, e);
            }
        }

        public Worker()
        {
        }

        public void DoLongTimeTask()
        {
            int i;
            bool t = false;
            double rate;

            OnStartWork(new StartWorkEventArgs(MAX) );

            for (i = 0; i <= MAX; i++)
            {
                Thread.Sleep(1);
                t = !t;
                rate = (double)i / (double)MAX;

                OnRateReport( new RateReportEventArgs(rate) );
            }

            OnEndWork( EventArgs.Empty );
        }

细细研读这段代码,不难理解它的算法。这里,使用了名为 handlers 的 Hashtable 存储外部挂接上的事件处理程序。每当事件处理程序被“add”,就把它加入到 handlers 里存储;相反 remove 时,就将它从 handlers 里移除。这里取 event 的 key (开始部分为每一种 event 都生成了一个 object 作为代表这种 event 的 key)作为 Hashtable 的键。

[TOP]

 

单播事件和多播事件

在 Demo 1G 给出的解决方案中,你或许已经注意到:如果某一事件被挂接多次,则后挂接的事件处理程序,将改写先挂接的事件处理程序。这里就涉及到一个概念,叫“单播事件”。

所谓单播事件,就是对象(类)发出的事件通知,只能被外界的某一个事件处理程序处理,而不能被多个事件处理程序处理。也就是说,此事件只能被挂接一次,它只能“传播”到一个地方。相对的,就有“多播事件”,对象(类)发出的事件通知,可以同时被外界不同的事件处理程序处理。

打个比方,上一节开头时张三大叫一声之后,既招来了救护车,也招来了警察叔叔(问他是不是回不了家了),或许还有电视转播车(现场直播、采访张三为什么大叫,呵呵)。

多播事件会有很多特殊的用法。如果以后有机会向大家介绍 Observer 模式,可以看看 Observer 模式中是怎么运用多播事件的。(注:经我初步测试,字段形式的事件声明,默认是支持“多播事件”的。所以如果在事件种类不多时,我建议你采用上一节中所讲的字段形式的声明方式。)

[TOP]

 

支持多播事件的改进

Demo1H,支持多播事件。为了支持多播事件,我们需要改进存储结构,请参考下面的算法:

        public delegate void StartWorkEventHandler(object sender, StartWorkEventArgs e);
        public delegate void RateReportEventHandler(object sender, RateReportEventArgs e);

        // 为每种事件生成一个唯一的键
        static readonly object StartWorkEventKey = new object();
        static readonly object EndWorkEventKey = new object();
        static readonly object RateReportEventKey = new object();

        // 为外部挂接的每一个事件处理程序,生成一个唯一的键
        private object EventHandlerKey
        {
            get { return new object(); }
        }

        // 对比 Demo 1G,
        // 为了支持“多播”,
        // 这里使用两个 Hashtable:一个记录 handlers,
        // 另一个记录这些 handler 分别对应的 event 类型(event 的类型用各自不同的 eventKey 来表示)。
        // 两个 Hashtable 都使用 handlerKey 作为键。

        // 使用 Hashtable 存储事件处理程序
        private Hashtable handlers = new Hashtable();
        // 另一个 Hashtable 存储这些 handler 对应的事件类型
        private Hashtable events = new Hashtable();

        protected void AddEventHandler(object eventKey, Delegate handler)
        {
            // 注意添加时,首先取了一个 object 作为 handler 的 key,
            // 并分别作为两个 Hashtable 的键。

            lock(this)
            {
                object handlerKey = EventHandlerKey;
                handlers.Add( handlerKey, handler );
                events.Add( handlerKey, eventKey);
            }
        }

        protected void RemoveEventHandler(object eventKey, Delegate handler)
        {
            // 移除时,遍历 events,对每一个符合 eventKey 的项,
            // 分别检查其在 handlers 中的对应项,
            // 如果两者都吻合,同时移除 events 和 handlers 中的对应项。
            //
            // 或许还有更简单的算法,不过我一时想不出来了 :(

            lock(this)
            {
                foreach ( object handlerKey in events.Keys)
                {
                    if (events[ handlerKey ] == eventKey)
                    {
                        if ( (Delegate)handlers[ handlerKey ] == handler )
                        {
                            handlers.Remove( handlers[ handlerKey ] );
                            events.Remove( events[ handlerKey ] );
                            break;
                        }
                    }
                }
            }
        }

        protected ArrayList GetEventHandlers(object eventKey)
        {
            ArrayList t = new ArrayList();

            lock(this)
            {
                foreach ( object handlerKey in events.Keys )
                {
                    if ( events[ handlerKey ] == eventKey)
                    {
                        t.Add( handlers[ handlerKey ] );
                    }
                }
            }

            return t;
        }

        // 使用了 add 和 remove 访问器的事件声明
        public event StartWorkEventHandler StartWork
        {
            add { AddEventHandler(StartWorkEventKey, value); }
            remove { RemoveEventHandler(StartWorkEventKey, value); }
        }

        public event EventHandler EndWork
        {
            add { AddEventHandler(EndWorkEventKey, value); }
            remove { RemoveEventHandler(EndWorkEventKey, value); }
        }
        
        public event RateReportEventHandler RateReport
        {
            add { AddEventHandler(RateReportEventKey, value); }
            remove { RemoveEventHandler(RateReportEventKey, value); }
        }

        // 此处需要做些相应调整
        protected virtual void OnStartWork( StartWorkEventArgs e )
        {
            ArrayList handlers = GetEventHandlers( StartWorkEventKey );

            foreach(StartWorkEventHandler handler in handlers)
            {
                handler(this, e);
            }
        }

        protected virtual void OnEndWork( EventArgs e )
        {
            ArrayList handlers = GetEventHandlers( EndWorkEventKey );

            foreach(EventHandler handler in handlers)
            {
                handler(this, e);
            }
        }

        protected virtual void OnRateReport( RateReportEventArgs e )
        {
            ArrayList handlers = GetEventHandlers( RateReportEventKey );

            foreach(RateReportEventHandler handler in handlers)
            {
                handler(this, e);
            }
        }

上面给出的算法,只是给你做参考,应该还有比这个实现更简单、更高效的方式。

为了实现“多播事件”,这次使用了两个 Hashtable:一个存储“handlerKey - handler”对,一个存储“handlerKey - eventKey”对。相信通过仔细研读,你可以读懂这段代码。我就不再赘述了。

[TOP]

打印 | 张贴于 2005-01-22 18:35:00 | Tag:暂无标签

留言反馈

#回复: .NET 事件模型教程(二) 编辑
我用字典存储事件实例的方法重写了Worker类,如下:
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace percyboy.EventModelDemo.Demo1K
{
public class Worker
{
private const int MAX = Consts.MAX;
private Dictionary<string, System.Delegate> eventTable;

#region EventArgs classes
public class StartWorkEventArgs : EventArgs
{
private int totalUnits;

public int TotalUnits
{
get { return totalUnits; }
}

public StartWorkEventArgs(int totalUnits)
{
this.totalUnits = totalUnits;
}
}

public class RateReportEventArgs : EventArgs
{
private double rate;

public double Rate
{
get { return rate; }
}

public RateReportEventArgs(double rate)
{
this.rate = rate;
}
}
#endregion

public delegate void StartWorkEventHandler(object sender, StartWorkEventArgs e);
public delegate void RateReportEventHandler(object sender, RateReportEventArgs e);

public Worker()
{
eventTable = new Dictionary<string, Delegate>();
eventTable.Add("StartWork", null);
eventTable.Add("RateReport", null);
eventTable.Add("EndWork", null);
}

public event StartWorkEventHandler StartWork
{
add
{
eventTable["StartWork"] = (StartWorkEventHandler)eventTable["StartWork"] + value;
}
remove
{
eventTable["StartWork"] = (StartWorkEventHandler)eventTable["StartWork"] - value;
}
}

public event EventHandler EndWork
{
add
{
eventTable["EndWork"] = (EventHandler)eventTable["EndWork"] + value;
}
remove
{
eventTable["EndWork"] = (EventHandler)eventTable["EndWork"] - value;
}
}

public event RateReportEventHandler RateReport
{
add
{
eventTable["RateReport"] = (RateReportEventHandler)eventTable["RateReport"] + value;
}
remove
{
eventTable["RateReport"] = (RateReportEventHandler)eventTable["RateReport"] - value;
}
}
protected virtual void OnStartWork(StartWorkEventArgs e)
{
StartWorkEventHandler temp;
if (null != (temp = (StartWorkEventHandler)eventTable["StartWork"]))
{
temp(this, e);
}
}

protected virtual void OnEndWork(EventArgs e)
{
EventHandler temp;
if (null != (temp = (EventHandler)eventTable["EndWork"]))
{
temp(this, e);
}
}

protected virtual void OnRateReport(RateReportEventArgs e)
{
RateReportEventHandler temp;
if (null != (temp = (RateReportEventHandler)eventTable["RateReport"]))
{
temp(this, e);
}
}

public void DoLongTimeTask()
{
int i;
bool t = false;
double rate;

OnStartWork(new StartWorkEventArgs(MAX));

for (i = 0; i <= MAX; i++)
{
Thread.Sleep(1);
t = !t;
rate = (double)i / (double)MAX;

OnRateReport(new RateReportEventArgs(rate));
}

OnEndWork(EventArgs.Empty);
}
}
}
在测试时却发现触发事件后,尽管执行了处理程序,但其执行效果却没有显示出来,即执行了this.statusBar1.Text = String.Format("已完成 {0:P0} ....", e.Rate);但向界面报告的效果没有显示,不知何故?使用 Console.WriteLine("");在调试时是可以看到效果的,另外,这个也支持多播。
2007-12-05 10:27:00 | [匿名用户:姚建友]
#回复: .NET 事件模型教程(二) 编辑
用EventHandlerList实现事件执行顺序符合先进先出原则
2007-10-28 11:40:00 | [匿名用户:^_^]
#回复: .NET 事件模型教程(二) 编辑
自己不需要使用hashtable存储事件吧,.net类库的System.ComponentModel空间下有个保护EventHandlerList类。

public sealed class EventHandlerList : IDisposable
{
// Fields
private ListEntry head;
private Component parent;

// Methods
public EventHandlerList()
{
}

internal EventHandlerList(Component parent)
{
this.parent = parent;
}

public void AddHandler(object key, Delegate value)
{
ListEntry entry = this.Find(key);
if (entry != null)
{
entry.handler = Delegate.Combine(entry.handler, value);
}
else
{
this.head = new ListEntry(key, value, this.head);
}
}

public void AddHandlers(EventHandlerList listToAddFrom)
{
for (ListEntry entry = listToAddFrom.head; entry != null; entry = entry.next)
{
this.AddHandler(entry.key, entry.handler);
}
}

public void Dispose()
{
this.head = null;
}

private ListEntry Find(object key)
{
ListEntry head = this.head;
while (head != null)
{
if (head.key == key)
{
return head;
}
head = head.next;
}
return head;
}

public void RemoveHandler(object key, Delegate value)
{
ListEntry entry = this.Find(key);
if (entry != null)
{
entry.handler = Delegate.Remove(entry.handler, value);
}
}

// Properties
public Delegate this[object key]
{
get
{
ListEntry entry = null;
if ((this.parent == null) || this.parent.CanRaiseEventsInternal)
{
entry = this.Find(key);
}
if (entry != null)
{
return entry.handler;
}
return null;
}
set
{
ListEntry entry = this.Find(key);
if (entry != null)
{
entry.handler = value;
}
else
{
this.head = new ListEntry(key, value, this.head);
}
}
}

// Nested Types
private sealed class ListEntry
{
// Fields
internal Delegate handler;
internal object key;
internal EventHandlerList.ListEntry next;

// Methods
public ListEntry(object key, Delegate handler, EventHandlerList.ListEntry next)
{
this.next = next;
this.key = key;
this.handler = handler;
}
}
}
测试实现类
using System;
using System.ComponentModel;

namespace WebTestEvents
{
/// <summary>
/// 事件测试类。
/// </summary>
public class TestEvents
{
public TestEvents()
{
;
}

static TestEvents()
{
EventInit = new object();
EventLoad = new object();
EventUnload = new object();
}
private static readonly object EventInit;
private static readonly object EventLoad;
private static readonly object EventUnload;

private EventHandlerList _events;
protected EventHandlerList Events
{
get
{
if (this._events == null)
{
this._events = new EventHandlerList();
}
return this._events;
}
}

protected virtual void OnInit(EventArgs e)
{
if (this._events != null)
{
EventHandler handler = this._events[EventInit] as EventHandler;
if (handler != null)
{
handler(this, e);
}
}
}

protected virtual void OnLoad(EventArgs e)
{
if (this._events != null)
{
EventHandler handler = this._events[EventLoad] as EventHandler;
if (handler != null)
{
handler(this, e);
}
}
}

protected virtual void OnUnload(EventArgs e)
{
if (this._events != null)
{
EventHandler handler = this._events[EventUnload] as EventHandler;
if (handler != null)
{
handler(this, e);
}
}
}

public event EventHandler Init
{
add
{
this.Events.AddHandler(EventInit, value);
}
remove
{
this.Events.RemoveHandler(EventInit, value);
}
}

public event EventHandler Load
{
add
{
this.Events.AddHandler(EventLoad, value);
}
remove
{
this.Events.RemoveHandler(EventLoad, value);
}
}

public event EventHandler Unload
{
add
{
this.Events.AddHandler(EventUnload, value);
}
remove
{
this.Events.RemoveHandler(EventUnload, value);
}
}
}
}
2007-10-28 11:37:00 | [匿名用户:hailsorm]
#回复: .NET 事件模型教程(二) 编辑
谢谢,怎么一直未见你的改进版呢
2007-07-11 13:33:00 | [匿名用户:ilex]
#.NET技术-.NET理论资料-.NET理论资料 编辑
综合:http://210.27.12.83/jingpin_mms.asp
2007-07-04 14:00:00 | [匿名用户:JasonLi]
#回复: .NET 事件模型教程(二) 编辑
刚做了实验,挂接事件时使用"="是不允许的。呵呵。
2007-05-10 12:11:00 | [匿名用户:坐看云起]
#回复: .NET 事件模型教程(二) 编辑
我的理解,未经证实:
挂接事件时将运算符由"+="改为"=",事件应该就由多播变成单播了。
也就是说.NET代理机制本身是以某种方式实现了一个队列。
2007-05-10 10:55:00 | [匿名用户:座看云起]
#.NET 事件模型教程(二) 编辑
framework
2006-08-30 15:53:00 | [匿名用户:jelink]
#re: .NET 事件模型教程(二) 编辑
A对象能不能伪装B对象发出B对象的事件通知。
2005-08-12 15:35:00 | [匿名用户:ayongwust]
#re: .NET 事件模型教程(二) 编辑
代码下不了啊,谁有能否给我一份,感激不尽。。
gaofan628@yahoo.com.cn
2005-05-11 14:37:00 | [匿名用户:gaofan]
#.NET 事件模型教程 编辑
Ping Back来自:blog.csdn.net
2005-02-04 11:34:00 | [匿名用户:morepower]
#re: .NET 事件模型教程(二) 编辑
谢谢 wang_solaris 的建议!

我正在准备重写这一部分,因为已经有人给我指出了不确切的地方:
其实委托可以是多路的
这样的话,就没有必要分别存储多个委托,
而可以直接挂接在同一个委托实例上。

就是说,委托的一个实例可以同时挂接多个函数,
委托是具有 +=,-= 运算符的。
这一点,我写文章时不了解,给大家介绍的方法其实走了弯路。
所以我正在准备重写这一部分,暂时因为年关太忙无法马上动笔,请诸位见谅!
2005-01-31 11:51:00 | [匿名用户:破宝]
#对多播事件的一点意见。 编辑
看了一下对多播事件的处理方式,总体思路值得肯定,但在此处对用Hashtable来存储键值对觉得有些不妥。
一般按照传统采用非静态成员来标识事件类型的方式,当在客户端为一个事件预定多个事件处理函数的时候,是按照队列的方式来处理的(即先进先出原则)。
而在你的代码中采用了Hashtable就破坏了这个原则,因为对Hashtable的遍历并不是按照插入时的顺序进行的(见上面对events的遍历)。所以我建议换成其它支持按插入时顺序进行遍历的集合类型,比如ListDictionary是个选择,不过当事件很多而对性能要求又很高时,需考虑其它实现。(当然上面程序中的handlers仍然可以使用Hashtable)
2005-01-31 11:25:00 | [匿名用户:wang_solaris]
#re: .NET 事件模型教程(二) 编辑
good
2005-01-25 21:51:00 | [匿名用户:Hoo]
对不起,目前本随笔不允许发表新评论.

Powered by: Joycode.MVC引擎 0.5.1.0