事件链






4.77/5 (32投票s)
执行多播委托以创建事件链,该链可以由链中的任何处理程序终止

引言
最近我需要一个多播委托(换句话说,一个事件),它能够足够智能,在链中的一个委托处理了事件后,就停止调用后续的委托。换句话说,我需要一个事件链,一旦链中的某个委托表明它已经处理了事件,就停止执行。我为此做了一些短暂的搜索,但没有找到任何东西,这让我感到惊讶,原因有二。首先,我认为应该有人已经实现了这个功能,其次,我以为这个功能在 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日:初次发布