|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
IntroductionRecently I needed a multicast delegate (an event, in other words) that was smart enough to stop calling the delegates in the invocation list when one of the delegates handled the event. In other words, I needed an event chain that stopped once a delegate in the chain indicated that it handled the event. I did some brief searching for this but didn't find anything, which surprises me for two reasons. First, I figured someone would have implemented this, and second, I thought this was implemented in C# 3.0. Perhaps I didn't look hard enough. Regardless, I needed this implementation for C# 2.0 and the .NET 2.0 Framework because that's what my code base currently requires. The CodeAs part of a previous article about an event pool, I had some code that was borrowed from Juval Lowy and Eric Gunnerson's best practices in defensive event firing mechanism, and Pete O'Hanlon upgraded this code to .NET 2.0 a while back. The code here is a modification of the safe event caller. Note: You will be amused though that I removed the The IEventChain InterfaceThe critical aspect of this implementation is the /// <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; }
}
This interface has one property, The EventChainHandlerDlgt DefinitionThe application uses this generic definition to specify the multicast delegate. /// <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;
The generic type An Example Argument ClassThe following illustrates the most basic implementation of an argument class suitable for the delegate described above: public class SomeCustomArgs : EventArgs, IEventChain
{
protected bool handled;
public bool Handled
{
get { return handled; }
set { handled = value; }
}
}
In the code download, you'll see that I added another property and field simply for testing that the same argument instance is passed to all methods in the invocation list (as one would expect.) Defining The Event SignatureA typical event signature looks like this: public static event EventChainHandlerDlgt<SomeCustomArgs> ChainedEvent;
(The event is The event signature of course defines the method handler signature as well. So, when we say: ChainedEvent += new EventChainHandlerDlgt<SomeCustomArgs>(Handler1);
The handler signature is expected to match the delegate as well (an example): public static void Handler1(object sender, SomeCustomArgs args)
{
Console.WriteLine("Handler1");
}
The EventChain ClassThe /// <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;
}
}
Note that this method returns a boolean indicating whether one of the event sinks in the chain flagged that it handled the event. ExampleThe screenshot above is the result of this code example (part of the download): 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");
}
The point of this code is that Again, note that all these methods are ConclusionThis code is simple enough that I felt it warrants being put in the "Beginner" section, yet illustrates a useful technique that extends the multicast delegate behavior. And as I mentioned in the introduction, it really does surprise me that someone hasn't done this before, and perhaps better, so if anyone has any other references to other implementations, please post the link in the comments for this article. AfterthoughtsThere are some interesting things one can do with this code. First off, it might be possible to optimize the invocation (how, I'm not sure right now). Also, one can change the order in which the invocation list is called. One can reverse the order, it can be based on some priority that is defined by the handler, and so forth. The handlers could be called as worker threads. All sorts of interesting possibilities are available when one has access to the invocation list! History
| ||||||||||||||||||||