65.9K
CodeProject 正在变化。 阅读更多。
Home

一个事件池

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.68/5 (41投票s)

2004年2月22日

CPOL

4分钟阅读

viewsIcon

158010

downloadIcon

3099

事件池有助于管理大量事件,这些事件否则会使您的代码杂乱无章并导致维护困难。

有关 .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 模型时更易于维护,并且更易于使用。

© . All rights reserved.