Wednesday, November 5, 2008

An Event Monitor Class



I recently received a question on the forums regarding the creation of an anonymous event handler method. Pretty much every event in the .NET Framework employs the same signature, with slight variations. According to the EventHandler page on MSDN, the standard signature for an event handler is a method that returns void, takes object as the first parameter, and takes a type that has been derived from EventArgs as it's second parameter. Microsoft has adhered to this rule with excellent consistency. Because of this, the ability to attach to every single event within a specific class is not too terribly difficult to do.

Nevertheless, I've found that at times, I've wanted to know which event is actually being raised. Perhaps I want to handle all the events in a single method, but I want to know for certain the name of the event raised on the object to which I'm subscribing. This is what I've created below.

First, I created an AnonymousEventArgs class, which itself derives from EventArgs, and contains two properties: EventName, which carries the name of the event raised, and InnerEventArgs, which contains the original EventArgs value that was created when the event was raised. It looks like this:


public class AnonymousEventArgs : EventArgs
{
public AnonymousEventArgs(string eventName, EventArgs innerEventArgs)
{
EventName = eventName;
InnerEventArgs = innerEventArgs;
}

public EventArgs InnerEventArgs { get; private set; }

public string EventName { get; private set; }
}
Next, I created a single class called "EventMonitor". EventMonitor has, at it's heart, a class called "AnonymousEventHandlerContainer" This class is designed as a generic class, with a type parameter that must inherit from EventArgs. This type parameter is used as the second parameter of the "Handler" method, which is the event handler for the specific event it is subscribed to. Each event on the object passed in as the sender in the EventMonitor will have one AnonymousEventHandlerContainer created for it, and that object will subscribe to only one event per instance. It is a nested class, and it looks like this:


public class AnonymousEventHandlerContainer<eventargs> where eventargs : EventArgs
{
// This is the name of the event subscribed to.
public string EventName { get; private set; }

// This references the parent EventHandler.
public EventMonitor Parent { get; private set; }

public AnonymousEventHandlerContainer(EventMonitor parent, string eventName)
{
EventName = eventName;
Parent = parent;
}

// This the generic handler that is used to subscribe to the object.
public void Handler(object sender, eventargs e)
{
if (Parent != null && Parent.Event != null)
Parent.Event(sender, new AnonymousEventArgs(EventName, e));
}
}
The EventMonitor is constructed by passing in the object whose methods you wish to subscribe to. It contains four methods, two of which subscribe or unsubscribe to specific events and two that subscribe to all the events on the object.

In addition to this, EventMonitor contains one event of type EventHandler<AnonymousEventArgs>, which is used for the creating class to subscribe to all events in the passed in object. The entire class looks like this:

public class EventMonitor
{
public EventMonitor(object sender)
{
Sender = sender;
}

public event EventHandler<AnonymousEventArgs> Event;

public object Sender { get; private set; }

private Dictionary<string, Delegate> eventHandlers = new Dictionary<string, Delegate>();

private int EventCount
{
get
{
return eventHandlers.Count;
}
}

/// <summary>
/// Subscribes to every event on the Sender.
/// </summary>
public void AttachAllEvents()
{
Sender.GetType().GetEvents().ToList().ForEach(e => AttachEvent(e.Name));
}

/// <summary>
/// Unsubscribes from every event on the Sender.
/// </summary>
public void DetachAllEvents()
{
Sender.GetType().GetEvents().ToList().ForEach(e => DetachEvent(e.Name));
}

/// <summary>
/// Stops monitoring a specific event.
/// </summary>
/// <param name="eventName">The name of the event to stop subscribing to. </param>
public void DetachEvent(string eventName)
{
if (eventHandlers.ContainsKey(eventName))
{
// Get the event information for the specified event.
EventInfo eventInfo = Sender.GetType().GetEvent(eventName);

if (eventInfo != null)
{
// Unsubscribe from the event.
eventInfo.RemoveEventHandler(Sender, eventHandlers[eventName]);

// Remove the handler method from the list of handler methods.
eventHandlers.Remove(eventName);

}
}
}

/// <summary>
/// Adds a generic event handler to any event whose type subscribes to the standard
/// event handler signature. (First parameter: object, second parameter inherits
/// from EventArgs).
/// </summary>
/// <param name="eventName">The name of the event to subscribe to.</param>
public void AttachEvent(string eventName)
{
// Don't subscribe twice.
if (!eventHandlers.ContainsKey(eventName))
{

// Get the event information for the specified event.
EventInfo eventInfo = Sender.GetType().GetEvent(eventName);

if (eventInfo != null)
{

// Get the handler type for the specified event.
Type handlerType = eventInfo.EventHandlerType;

// Get the ParameterInfo collection for the event handler's invoke method.
ParameterInfo[] parameterInfos = handlerType.GetMethod("Invoke").GetParameters();

// Get the collection of types corresponding to the event handler's parameters.
Type[] parameterTypes = parameterInfos.Select(p => p.ParameterType).ToArray();

// Only attach a handler to methods that conform to the standard event handler signature
// of two parameters, one being an object sender, and the second being derived from
// eventargs.
if (parameterTypes.Length == 2 && parameterTypes[0] == typeof(object) &&
typeof(EventArgs).IsAssignableFrom(parameterTypes[1]))
{
// Create an array of generic type parameters.
Type[] genericTypeParameters = new Type[] { parameterTypes[1] };

// Get the type of the AnonymousDelegateClass, and fill the generic parameters.
Type type = typeof(AnonymousEventHandlerContainer<>).MakeGenericType(genericTypeParameters);

Type[] constructorTypes = new Type[] { typeof(EventMonitor), typeof(string) };

// Get the constructor for the class that takes the string value.
ConstructorInfo constructor = type.GetConstructor(constructorTypes);

// Create an instance of the class, which will subscribe to the event specified.
object instance = constructor.Invoke(new object[] { this, eventName });

// Get the Handler method, which will be the event handler for the event.
MethodInfo method = type.GetMethod("Handler");

// Create a delegate of the same type of the handler type based on the Handler method.
Delegate eventhandler = Delegate.CreateDelegate(handlerType, instance, method);

// Add the event handler to the object specified.
eventInfo.AddEventHandler(Sender, eventhandler);

// Finally, record the method subscriber in case we need to remove it.
eventHandlers.Add(eventName, eventhandler);
}
}
}
}


public class AnonymousEventHandlerContainer<eventargs> where eventargs : EventArgs
{
// This is the name of the event subscribed to.
public string EventName { get; private set; }

// This references the parent EventHandler.
public EventMonitor Parent { get; private set; }

public AnonymousEventHandlerContainer(EventMonitor parent, string eventName)
{
EventName = eventName;
Parent = parent;
}

// This the generic handler that is used to subscribe to the object.
public void Handler(object sender, eventargs e)
{
if (Parent != null && Parent.Event != null)
Parent.Event(sender, new AnonymousEventArgs(EventName, e));
}
}
}

Now, how to use the class. I tested this class by creating a new Windows Forms application, adding a single RichTextBox called "richTextBox1" and adding a DateTimePicker called "dateTimePicker1" onto the form. I then attached the DateTimePicker to the EventMonitor class. My form looked like this:

public partial class Form1 : Form
{
EventMonitor monitor;

public Form1()
{
InitializeComponent();
monitor = new EventMonitor(dateTimePicker1);
monitor.AttachAllEvents();
monitor.Event +=
new EventHandler<AnonymousEventArgs>(monitor_Event);

}

void monitor_Event(object sender, AnonymousEventArgs e)
{
richTextBox1.AppendText(string.Format("{0}: {1}{2}",
DateTime.Now.ToShortTimeString(),
e.EventName,
Environment.NewLine));
}
}
If you run this application, you'll notice output from every event that is raised on the DateTimePicker. The event name is displayed in the RichTextBox, along with the time it was raised.

I have found this class to be helpful in determining which events are raised, whether events are raised, and in what order they're raised.