自定义事件类型

时间:2022-02-02 16:09:20

/*

*

* 自定义事件类型

*

*

*

* 摘抄于:<Microsoft.net框架设计>

* email:suiqirui1987@163.com

*

* QQ:492732033

*

* copyright:@@@@ suiqirui19872005.cnblogs.com

*

*

*

* 思想:

*

* 让每个对象都保存一个事件/委托对的集合(通常为一个散列表).当一个新的对象

*

* 被构造时.该集合是空的.当有事件被登记时.我们将在集合中查找该事件

*

* .如果集合中存在该事件.那新的委托实例将衩组全到表示该事件的委托链表上.否则,该事件和新的委托实例将直接被

*

* 添加到集合中..

*

*

* 当事件需要触发时,我们还是首先在集合中查找该事件.如果集合不存在该事件.也就是说

*

* 该事件没有被登记.那么将没有任何委托实例被调用..如果集合中存在该事件.

*

* 那么其对应的委托链表将被调用

*

*

*

*/

 

 

 

using System;

using System.Data;

using System.Configuration;

using System.Web;

using System.Web.Security;

using System.Web.UI;

using System.Web.UI.WebControls;

using System.Web.UI.WebControls.WebParts;

using System.Web.UI.HtmlControls;

using System.Collections;

using System.Runtime.CompilerServices;

 

#region EventHandlerSet类

 

 

/*

* --------> EventHandlerSet类 <-----------

*

* 注意:

* FCL中定义有一个名为System.ComponentModel.EventHandlerList的类型。该类型和上面展示的EventHandlerSet

*

* 类型所做的事情基本上是一样的。System.Window.Forms.Control和System.Web.UI.Control 类型在维护它们各自的稀疏事件集合时

*

* 使用的就是EventHandlerList这个类型。。 大家当然可以选择使用FCl中EventHandlerList 类型。该类型和我们上面展示的EventHandlerSet

*

* 类型的不同之处在于EventHandlerList类型在内部使用的是一个链表,而不是散列表

*

* 这意味着访问EventHandlerList类型中的元素要比访问EventHandlerSet类型中的元素慢一些.

*

* 另外.EventHandlerList类型没有提供任何线程安全的访问方式.因此在某些情况下.

*

* 大家还需要自已实现一个对EventHandlerList类型的线程安全的封装

*

*

*

*

*/

public class EventHandlerSet : IDisposable

{

//用于保存"事件键/委托值"对的私有散列表

private Hashtable events = new Hashtable();

//一个索引器,用获取或设置与传入的事件对象的

//散列键相关联的委托

public virtual Delegate this[Object eventKey] {

//如果对象不在集合中,则返回null

get {

 

return (Delegate)events[eventKey];

}

set {

 

events[eventKey] = value;

}

}

 

//指定的事件对象的散列刍对应的委托链表上添加/组全一个委托实例

public virtual void AddHandler(object eventKey, Delegate handler) {

 

events[eventKey] = Delegate.Combine((Delegate)events[eventKey], handler);

}

public virtual void RemoveHandler(Object eventKey, Delegate handler) {

 

events[eventKey] = Delegate.Remove((Delegate)events[eventKey], handler);

}

//在指定的事件对象散列键盘对应的委托链表上触发事件

public virtual void Fire(Object eventKey, Object sender, EventArgs e) {

 

Delegate d = (Delegate)events[eventKey];

 

if (d != null) {

 

d.DynamicInvoke(new object[] { sender, e });

}

}

 

//释放对象以使散列表占用的内存资源在下一次垃圾收集中被回收,从而阻止垃圾收集器提升其性能

 

public void Dispose()

{

 

events = null;

 

 

 

}

 

 

//下而的静态方法返回一个对传入的EventHandlerSet对象的线程安全的封装

public static EventHandlerSet Syschronized(EventHandlerSet eventHandlerSet)

{

 

if (eventHandlerSet == null)

{

 

throw new ArgumentNullException("eventHanlderSet");

}

return new SysnchronizedEventHandlerSet(eventHandlerSet);

 

}

 

//下面类在EventHandlerSet类的基础上提供了一个线程安全的封装

private class SysnchronizedEventHandlerSet : EventHandlerSet{

 

//引用非线程安全的对象

private EventHandlerSet eventHandlerSet;

 

 

//在非线程安全的对象上构造一个线程安全的封装

public SysnchronizedEventHandlerSet(EventHandlerSet eventHandlerSet){

 

 

this.eventHandlerSet=eventHandlerSet;

Dispose();//释放基类中的散列表对象

}

 

 

 

//线程安全的索引器

public override Delegate this[object eventKey]

{

 

[System.Runtime.CompilerServices.MethodImpl(MethodImplOptions.Synchronized)]

get

{

return eventHandlerSet[eventKey];

 

}

[MethodImpl(MethodImplOptions.Synchronized)]

set

{

eventHandlerSet[eventKey] = value;

 

 

}

}

 

//线程安全的AddHander方法

[MethodImpl(MethodImplOptions.Synchronized)]

public override void AddHandler(object eventKey, Delegate handler)

{

eventHandlerSet.AddHandler(eventKey, handler);

//base.AddHandler(eventKey, handler);

}

 

//线程安全的RemoveHandler方法

[MethodImpl(MethodImplOptions.Synchronized)]

public override void RemoveHandler(object eventKey, Delegate handler)

{

RemoveHandler(eventKey, handler);

 

//base.RemoveHandler(eventKey, handler);

}

 

//线程安全的Fire方法

 

//该方法首先从events 散列表中找到一个委托链表,然后调用该委托链表上封装的所有的回调方法

/*

* 因为散列表中可能包含有各种各样的不同的委托类型。所以我们不可能在编译进行类型安全

* 的委托调用。因此,我们使用了System.Delegate 的DynamicInvoke方法,交将回调方法的参数

* 组全为一个对象数组传递给它。

* DynamicInvoke方法在内部会检测回调方法期望的参数和我们传递的参数

 

* 是否匹配。如果匹配,回调方法将被调用

*

* 否则。DynamicInvoke方法将抛出一个异常.

*/

public override void Fire(object eventKey, object sender, EventArgs e)

{

eventHandlerSet.Fire(eventKey, sender, e);

//base.Fire(eventKey, sender, e);

}

 

}

 

 

 

}

 

#endregion

 

 

 

#region TypeWithLotsOfEvents类

 

 

/*

* 实现步骤

*

* 1.定义一个受保护的实例集合字段

*

* TypeWithLotsOfEvents类型的每个实例都会使用该集合字段来保存其上的事件侦听者

* 集合.这里的的集合通常使用散列表来实现,因为这可以提供比较快的事件查找速度.集合中的第个元素

* 都有一个键(类型通常为System.Object)用以唯一地标识事件类型.以及一个和刍对应的值用以标识事件

* 触发时要调用的委托链表.将该字段的访问限制设为了protected的目的是让派生类型也可以使用集合来为自已定义的一个类型

* ,它在内部使用了一个System.Collections.Hashtable对象.EventHandlerSet 类型还有一些成员负责处理事件和委托实例

*

*

*

* 2.为类型希望提供的事件定义必要的成员

* 对于我们希望类型提供的每个事件.我们必须为其定义一组成员.大家可以已经注意到了

* 我们没有使用实例字段-----它们正是浪费内存的根源

*

* 2.1 构造一个静态只读对象来标识事件

* 我们必须采取一种方式唯一地标识集合中的每一个事件.因为我们使用的是一个散列表.所以我们需要一个

* 唯一的键.这可以由一个对象的散列码产生.

* TypeWithlotsOfEvents类型的所有实例将共享这个惟一的键.所以我们构造了一个Object,并将其引用保存在一个

* 静态只读字段中.只有在TypeWithlotsOfEvents类型的用户构造多个TypeWithLotsOfEvents.这种技巧才会节省更多的内存.

* 如果用户只构造一个TypeWithLotsOfEvents类型实例.这种技巧实际上会耗费更多的内存.如果是这种情况.就不应该使用这里的

* 演示的技巧.另外,本例中将这个静态字段定义为受保护字段.目的是为了让派生类型也能使用它.当然,大家也可以奖其定义为私

* 有字段.

*

* 2.2 为事件定义一个继承自EventArgs的类型,该类型用于保存任何需要传递给事件接受者的附加信息

* 如果事件没有什么特殊的信息需要传递,我们可以简单的使用FCL中的System.EventArgs类型就可以了.本例仅仅是处于演示的目的

* 因此就没有在其中定义任何字段.

*

* 2.3 定义一个委托类型,指定事件触发时被调用的方法原型

* 由于FooEventArgs类型没有定义任何段,所以实际我们不需要定义一个特殊的委托类型.如果事件没有特殊的信息需要传递给侦听者.

* 我们可以直接使用FCL中定义中System.EventHandler类型;

*

* 2.4 显式定义事件及其访问器方法

* 这个整个技艺中比较有趣的部分,在该步骤中,我们为类型希望提供的每一个事件显式定义了add和remove访问器方法

* add 和remove访问器分别调用了EventHandlerSet类中的AddHandler和RemoveHandler方法.EventHandlerSet类型的AddHandler

* 方法会扫描集合看其中是否存在fooEventKey键对象.如果不存在.它将被添加到散列表中.事件被触发时调用的也将是该键对象

* 所标识的委托实例.如果存在,传入的值(新的委托实例)将被组全到已经存在的委托链表上.RemoveHandler方法所做的操作正好相反.

* 在本例中,所有对EventHandlerSet 集合的访问都是线程安全.所以我们不必再在事件的add和remove访问器方法上指定MethodImpAttribute特性

*

* 2.5 定义一个受保护的虚方法,负责通知事件的登记对象

* 不管何时出现"Foo事件",OnFoo方法都会被调用,在本例中,OnFoo方法调用了EventHandlerSet类型的Fire方法,Fire方法会集合中查找传入

* 键对象.如果找到.它将调用与其相关联的委托链表.其中传递给委托链表的参数为this(触发事件的对象)

* 和表示附加信息的继承自EventArgs的类型实例.

*

* 2.6 定义一个方法将输入转化期望的事件

* 本章前面曾经讨论该方法和它的用法.本例中,我们调用了解SimulateFoo方法来模拟出现了一个"Foo事件".该方法所有工作就是

* 传递必要的附加信息.然后触发事件

*

* 3. 为类型希望提供的事件定义必要的成员

*

*

*/

 

//下面的代码在一个类型中定义多个事件时推荐的设计模式

 

 

class TypeWithLotsOfEvents {

 

//定义一个受保护的实例字段,该字段引用一个集合来管理

//一组事件/委托。注意。类型EventHandlerSet并非们于FCL中,

protected EventHandlerSet eventSet = EventHandlerSet.Syschronized(new EventHandlerSet());

 

/*

* 2.为Foo事件定义必要的成员.

*

* 2.1 构造一个静态的只读对象来标识该事件.每个对象都有

*

* 一个散列码.用于集合中查找事件对应的委托链表

*

*

*

*/

 

protected static readonly Object fooEventKey = new object();

 

/*

* 2.2 为事件定义一个继承自EventArgs类型

*

*

*/

 

public class FooEventArgs:EventArgs

{

 

}

 

/*

*

* 2.3 为事件定义委托原型

*

*/

public delegate void FooEventHandler(Object sender, FooEventArgs e);

 

/*

* 2.4 为事件定义访问器方法用于在集合上添加/移除委托实例

*

*/

 

public event FooEventHandler Foo {

 

add {

 

eventSet.AddHandler(fooEventKey, value);

}

remove {

eventSet.RemoveHandler(fooEventKey, value);

 

}

}

 

 

/*

* 2.5 为事件定义一个受保护的虚方法(OnXXXX)

*

*/

protected virtual void OnFoo(FooEventHandler e) {

 

eventSet.Fire(fooEventKey, this, e);

}

 

 

/*

* 2.6 定义一个方法将输入转化为期望的事件

*

*/

public void SimulateFoo() {

 

OnFoo(new FooEventArgs());

}

 

/*

* 3 为Bar事件定义必要的成员

*

*

* 3.1 构造一个静态只读对象来标识该事件.每个对象都有一个

*

* 散列友用于集合中查找事件对应的委托链表

*/

protected static readonly Object barEventKey = new object();

 

/*

* 3.2 为事件定义一个继承自EventArgs的类型

*

*/

public class BarEventArgs : EventArgs { }

 

 

/*

* 3.3 为事件定义一个委托原型

*/

 

public delegate void BarEventHander(object sender, BarEventArgs e);

 

/*

* 3.4 为事件定义一个受保护的虚方法(Onxxx)

*

*

*/

protected virtual void OnBar(BarEventArgs e) {

 

eventSet.Fire(barEventKey, this, e);

}

 

//3.5 定义一个方法将输入转化为期望事件

 

 

public void SimulateBar() {

OnBar(new BarEventArgs());

}

}

 

#endregion