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

事件链

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.77/5 (32投票s)

2008年7月1日

CPOL

4分钟阅读

viewsIcon

119367

downloadIcon

378

执行多播委托以创建事件链,该链可以由链中的任何处理程序终止

EventChain

引言

最近我需要一个多播委托(换句话说,一个事件),它能够足够智能,在链中的一个委托处理了事件后,就停止调用后续的委托。换句话说,我需要一个事件链,一旦链中的某个委托表明它已经处理了事件,就停止执行。我为此做了一些短暂的搜索,但没有找到任何东西,这让我感到惊讶,原因有二。首先,我认为应该有人已经实现了这个功能,其次,我以为这个功能在 C# 3.0 中已经实现了。也许我没有搜得足够仔细。无论如何,我需要为 C# 2.0 和 .NET 2.0 Framework 实现这个功能,因为我的代码库目前需要它。

代码

作为之前关于事件池的文章的一部分,我借鉴了 Juval Lowy 和 Eric Gunnerson 在防御性事件触发机制方面的最佳实践中的一些代码,而 Pete O'Hanlon 曾经在 一段时间前将这段代码升级到了 .NET 2.0。这里的代码是对安全事件调用者的修改。

注意:你会觉得好笑的是,我移除了 try-catch 块,因为我现在认为异常不应该由这段代码捕获,而应该由调用者捕获,尤其是考虑到在安全事件调用者实现中,异常是被重新抛出的!

IEventChain 接口

此实现的关键方面是 IEventChain 接口,事件签名中的参数必须实现它。事件签名遵循 .NET 的标准实践 Handler(object sender, EventArgs args),其中“args”属性派生自 EventArgs 并实现了 IEventChain

/// <summary>
/// The interface that the argument parameter must implement.
/// </summary>
public interface IEventChain
{
  /// <summary>
  /// The event sink sets this property to true if it handles the event.
  /// </summary>
  bool Handled { get; set; }
}

此接口有一个属性 Handled,事件接收者可以将其设置为 true,如果它已处理该事件。事件调用方法在调用委托后会检查此属性,以查看该方法是否将其设置为 true

EventChainHandlerDlgt 定义

应用程序使用此泛型定义来指定多播委托。

/// <summary>
/// A generic delegate for defining chained events.
/// </summary>
/// <typeparam name="T">The argument type, must implement IEventChain.</typeparam>
/// <param name="sender">The source instance of the event.</param>
/// <param name="args">The parameter.</param>
public delegate void EventChainHandlerDlgt<T>
    (object sender, T arg) where T : IEventChain;

泛型类型 <T> 指定了参数类型。提供编译时类型验证的一个非常巧妙的功能是 where T : IEventChain 子句,它对泛型类型 <T> 施加了一个限制,即它必须实现 IEventChain

示例参数类

以下代码说明了适合上述委托的最基本参数类的实现。

public class SomeCustomArgs : EventArgs, IEventChain
{
  protected bool handled;

  public bool Handled
  {
    get { return handled; }
    set { handled = value; }
  }
}

在下载的代码中,你会看到我添加了另一个属性和字段,仅仅是为了测试,以确保相同的参数实例被传递给调用列表中的所有方法(正如人们所期望的那样)。

定义事件签名

典型的事件签名如下所示。

public static event EventChainHandlerDlgt<SomeCustomArgs> ChainedEvent;

(事件是 static 的,仅仅是因为在我的测试代码中,它属于 Main 方法的一部分,而 Main 方法是 static 的。)

事件签名当然也定义了方法处理程序的签名。所以,当我们说到

ChainedEvent += new EventChainHandlerDlgt<SomeCustomArgs>(Handler1);

处理程序的签名也需要与委托匹配(一个例子)。

public static void Handler1(object sender, SomeCustomArgs args)
{
  Console.WriteLine("Handler1");
}

EventChain 类

EventChain 类是一个 static 类,它实现了一个方法:Fire。代码注释解释了这个方法的运作方式,它确实非常简单。

/// <summary>
/// This class invokes each event sink in the event's invocation list
/// until an event sink sets the Handled property to true.
/// </summary>
public static class EventChain
{
  /// <summary>
  /// Fires each event in the invocation list in the order in which
  /// the events were added until an event handler sets the handled
  /// property to true.
  /// Any exception that the event throws must be caught by the caller.
  /// </summary>
  /// <param name="del">The multicast delegate (event).</param>
  /// <param name="sender">The event source instance.</param>
  /// <param name="arg">The event argument.</param>
  /// <returns>Returns true if an event sink handled the event,
  /// false otherwise.</returns>
  public static bool Fire(MulticastDelegate del, object sender, IEventChain arg)
  {
    bool handled = false;

    // Assuming the multicast delegate is not null...
    if (del != null)
      {
      // Get the delegates in the invocation list.
      Delegate[] delegates = del.GetInvocationList();

      // Call the methods until one of them handles the event
      // or all the methods in the delegate list are processed.
      for (int i=0; i<delegates.Length && !handled; i++)
      {
        // There might be some optimization that is possible
        // by caching methods.
        delegates[i].DynamicInvoke(sender, arg);
        handled = arg.Handled;
      }
    }

    // Return a flag indicating whether an event sink handled
    // the event.
    return handled;
  }
}

请注意,此方法返回一个布尔值,指示链中的某个事件接收者是否标记为已处理该事件。

示例

上方的截图是此代码示例(下载的一部分)的结果。

public static void Main()
{
  ChainedEvent += new EventChainHandlerDlgt<SomeCustomArgs>(Handler1);
  ChainedEvent += new EventChainHandlerDlgt<SomeCustomArgs>(Handler2);
  ChainedEvent += new EventChainHandlerDlgt<SomeCustomArgs>(Handler3);

  SomeCustomArgs args=new SomeCustomArgs();
  bool ret = EventChain.Fire(ChainedEvent, null, args);
  Console.WriteLine(args.N);
  Console.WriteLine(ret);
}

public static void Handler1(object sender, SomeCustomArgs args)
{
  ++args.N;
  Console.WriteLine("Handler1");
}

public static void Handler2(object sender, SomeCustomArgs args)
{
  ++args.N;
  Console.WriteLine("Handler2");
  args.Handled = true;
}

public static void Handler3(object sender, SomeCustomArgs args)
{
  ++args.N;
  Console.WriteLine("Handler3");
}

这段代码的要点是 Handler3 永远不会被调用,因为 Handler2 表明它已处理该事件。

再次请注意,所有这些方法都是 static 的,仅仅是因为将它们全部放在 Main 方法中很方便。

结论

这段代码足够简单,我认为它值得放在“初学者”部分,但它却展示了一种有用的技术,扩展了多播委托的行为。正如我在引言中提到的,我确实很惊讶竟然没有人之前做过这个,或许做得更好,所以如果有人有其他关于其他实现的参考,请在此文章的评论区发布链接。

事后思考

使用这段代码可以做一些有趣的事情。首先,可能可以优化调用(如何优化,我目前还不确定)。另外,可以改变调用列表的调用顺序。可以反转顺序,可以基于处理程序定义的某种优先级,等等。处理程序可以作为工作线程被调用。当你能够访问调用列表时,各种有趣的可能都会出现!

历史

  • 2008年7月1日:初次发布
© . All rights reserved.