一个事件池






4.68/5 (41投票s)
事件池有助于管理大量事件,这些事件否则会使您的代码杂乱无章并导致维护困难。
有关 .NET 2.0 版本,请参阅 Pete O'Hanlon 的文章。
前言
EventPool 是模型-视图-控制器 (MVC) 技术路线图系列的一部分,其中包括
- EventPool(本文)
- IMemento(待定)
- UndoBuffer(待定)
- StateManager(待定)
- XmlGuiGenerator
- Form2Xml(待定)
引言
当使用 MVC 模型时,事件池对于管理大量事件非常有用。一个丰富的模型通常包含许多属性并经历许多不同的状态更改。声明一个唯一的事件(有时还有一个带有子类化 `EventArgs` 参数的委托)非常耗时,并且会增加模型的维护负担。
这种方法的缺点是,尽管支持子类化的 `EventArgs`,但必须在事件处理程序中将其强制转换为正确的子类。这会将检测不正确构造的事件处理程序的时机从编译时移到运行时。
问题
以一个简单的模型为例,例如一个简单的 RPN 计算器使用的四寄存器堆栈。
protected string[] registerStack={"0.00", "0.00", "0.00", "0.00"};
我们希望提供一个事件,当任何寄存器值更改时触发该事件,以便任何关联的视图可以更新用户界面,例如
假设模型用四个属性表示事件
- X
- Y
- R3
- R4
通常,我们会为事件签名构建一个委托
public delegate void RegisterChangeDelegate(object sender, EventArgs args);
以及我们的四个事件
public event RegisterChangeDelegate RegisterXChangeEvent;
public event RegisterChangeDelegate RegisterYChangeEvent;
public event RegisterChangeDelegate RegisterR3ChangeEvent;
public event RegisterChangeDelegate RegisterR4ChangeEvent;
并在属性设置器中调用我们的事件。例如,X 寄存器设置器
public string X
{
get {return registerStack[0];}
set
{
registerStack[0]=value;
if (RegisterXChangeEvent != null)
{
RegisterXChangeEvent(this, EventArgs.Empty);
}
}
}
视图使用以下方式告知模型其对更改事件的兴趣
model.RegisterXChangeEvent+=new EventHandler(RegisterXChangeEventHandler);
这将允许我们的模型视图连接事件处理程序,当模型更改时调用它们,一切都会顺利进行。有三个问题
- 我们创建了事件定义,这些定义是维护的累赘(尽管在此示例中是一个小的累赘)。
- 除非程序员非常熟练,否则他/她会忘记添加事件,就像我在这里所做的一样。
- 我们必须记住始终检查事件处理程序是否已实际分配。
最后一个问题可以通过一些防御性的事件发布来解决,这是来自 Juval Lowy 和 Eric Gunnerson 的 C# 最佳实践 PowerPoint 演示文稿的功劳,可在此 处 下载
public class EventsHelper
{
public static void Fire(Delegate del,params object[] args)
{
if(del == null)
{
return;
}
Delegate[] delegates = del.GetInvocationList();
foreach(Delegate sink in delegates)
{
try
{
sink.DynamicInvoke(args);
}
catch{}
}
}
}
这消除了“if”块,并确保即使事件未分配,设置器仍然可以工作。例如,新的“X”设置器将如下所示
public string X
{
get {return registerStack[0];}
set
{
registerStack[0]=value;
EventsHelper.Fire(RegisterXChangeEvent);
}
}
干净了很多,但它仍然没有解决其他两个问题——大量的事件和缺乏仪表。
解决方案
解决方案是事件池。事件池简化了对许多不同委托及其关联事件和事件处理程序的管理。通过将事件处理程序与标签关联来抽象事件。通过引用标签(而不是事件委托)来调用事件。事件池还非常适合仪表,以便程序员不必记住这样做。
鉴于以上示例,当我们使用事件池时,以下代码将被消除
public delegate void RegisterChangeDelegate(object sender, EventArgs args);
public event RegisterChangeDelegate RegisterXChangeEvent;
public event RegisterChangeDelegate RegisterYChangeEvent;
public event RegisterChangeDelegate RegisterR3ChangeEvent;
public event RegisterChangeDelegate RegisterR4ChangeEvent;
模型通过标签机制触发任何已分配的事件。例如
public string X
{
get {return registerStack[0];}
set
{
registerStack[0]=value;
eventPool.FireEvent("X");
}
}
视图以略有不同的方式表达其对模型事件的兴趣
model.EventPool.Subscribe("X",new EventHandler(RegisterXChangeEventHandler));
松耦合
当然,危险在于,您可以订阅的事件现在与已发布的事件非常松散地关联。您如何知道您是否正在订阅一个仍然被发布的事件?
答案是告诉事件池您正在发布哪些事件,通过 `Publish` 方法
virtual public void Publish(params object[] tags)
{
foreach(object tag in tags)
{
eventTagToDelegateMap.Add(tag, null);
}
}
例如,模型发布它触发的事件
eventPool.Publish("X", "Y", "R3", "R4", "EntryStateChanged");
现在,当视图订阅模型的事件时,事件池将验证事件是否已发布,并在未发布时发出警告(或者您可以更改代码使其抛出异常)。 `Subscribe` 方法是重载的,因此订阅者可以选择禁用此检查,以防(哈哈)发布者尚未注册事件或正在进行动态事件发布。
演示
下载包含一个 RPN 计算器作为 `EventPool` 类的演示。此演示是一个非常干净的 MVC 模型,并且有许多正在进行的事件,这些事件由 `EventPool` 管理。
文档
有关 `EventPool` 的文档可以在我的 网站 上找到。这些文档将随着本系列其他文章的发布而扩展,以包含 MVC 模型中的其他技术。
关于 `EventPool` 中唯一有趣的部分是 `Subscribe` 方法,它构建了一个多播委托
virtual public void Subscribe(
object tag,
EventHandler eventHandler,
bool loose)
{
// do we verify that the handler exists?
if (!loose)
{
if (!eventTagToDelegateMap.Contains(tag))
{
Trace.WriteLine("Event Pool does not contain an entry for: "+
tag.ToString());
}
}
// create the delegate for this event handler
MulticastDelegate newDelegate=new MulticastDelegate(eventHandler);
// if this is a new tag...
if (!eventTagToDelegateMap.Contains(tag))
{
// ...add a new tag to the map.
eventTagToDelegateMap.Add(tag, newDelegate);
}
else
{
// ...otherwise, combine the new delegate with the delegates for
// existing mapping.
MulticastDelegate dlgt=(MulticastDelegate)eventTagToDelegateMap[tag];
// if the tag has associated delegates already...
if (dlgt != null)
{
// ... combine the new delegate with the existing ones.
dlgt=(MulticastDelegate)Delegate.Combine(dlgt, newDelegate);
// delegates are data structures, which are therefore passed by value.
eventTagToDelegateMap[tag]=dlgt;
}
else
{
// ... otherwise just assign the new delegate.
eventTagToDelegateMap[tag]=newDelegate;
}
}
}
结论
我个人发现这种方法在处理发布许多事件的 MVC 模型时更易于维护,并且更易于使用。