.NET 事件模型教程(二)

前端之家收集整理的这篇文章主要介绍了.NET 事件模型教程(二)前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

目录

  • 属性样式的事件声明
  • 单播事件和多播事件
  • 支持多播事件的改进
  • 属性样式的事件声明



在第一节中,我们讨论了.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访问器,自定义你自己的事件处理程序添加和移除的实现方法

Demo1G:“属性”样式的事件声明。

我首先给出一种实现方案如下(此实现参考了.NETFrameworkSDK文档中的一些提示)(限于篇幅,我只将主要的部分贴在这里):

复制 保存
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的键。

单播事件和多播事件

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

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

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

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

支持多播事件的改进

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

复制 // 为每种事件生成一个唯一的键 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”对。相信通过仔细研读,你可以读懂这段代码。我就不再赘述了。

猜你在找的VB相关文章