A Universal Event Handler Factory






4.83/5 (19 votes)
A factory, which dynamically creates helper classes to hook on events of any signature.
Introduction
One of the most powerful features of the .NET Framework is Reflection. It enables us to dynamically load any assembly, enumerate its class types and even instantiate a class and call its properties and methods. We can do all this without any prior knowledge of the assembly we want to use.
Of course, in practice, we mostly know something about the assemblies to use them reasonably. This can be the name of a class, the name of a method, or a custom attribute which is attached to a class or a method. For example, the testing framework NUnit parses assemblies for class and method attributes to determine the tests to be executed.
However, there is one point, which obscures the bright image of .NET reflection: Events! Imagine, you have an unknown assembly “UnknownAssembly.dll”, which holds an unknown class “EventEmitter
”. This class can emit some events and you want to be notified if one of the events of that class is fired. You even want to know the values of the arguments of the events.
Normally, one would attach an event handler to that event and this event handler would be called whenever the event is fired. But the event handler, which is to be attached to an event, must have the correct signature of that event. This is not a problem, if you know the delegates of the events you have to handle while you write your code. You simply write the corresponding event handlers. But if you have to deal with unknown events, you have a problem.
Universal Event Handler
I had this problem in a recent project and I tried to find a universal solution to it. My goal was that all events should be mapped to one single event handling method with the signature:
public delegate void CommonEventDelegate(Type EventType, object[] Arguments);
So, one would have access to all event related information. The EventType
holds the type information to the fired event and the Arguments
object array holds the arguments of the fired event. In case of an event without arguments, this array would be empty.
To bridge the gap between a certain event and the universal event handler, I developed a class factory, which creates at run time a special helper class for a given event and instantiates it.
For an event which has the delegate type:
public delegate void MyEventDelegate(int Counter, string Name, DateTime Time);
the C# representation of the generated helper class would look like:
public class MyEventHelper
{
private Type type;
public event CommonEventDelegate CommonEvent;
public MyEventHelper(EventInfo Info)
{
type = Info.EventHandlerType;
}
public void MyEventHandler(int Counter, string Name, DateTime Time)
{
object[] args = new object[3];
args[0] = Counter;
args[1] = Name;
args[2] = Time;
// Check, if event handler is attached to event
if (CommonEvent != null)
CommonEvent(type, args);
}
}
The Common Event Handler Library consists of an EventHandlerFactory
class, which holds all created helper classes and prevents double creation of a helper class. The real working horse is the EventHandlerTypeEmiter
class. It creates a dynamic assembly and a dynamic module to host the created helper classes.
AssemblyName myAsmName = new AssemblyName();
myAsmName.Name = assemblyName + "Helper";
asmBuilder = Thread.GetDomain().DefineDynamicAssembly(myAsmName,
AssemblyBuilderAccess.Run);
helperModule = asmBuilder.DefineDynamicModule(assemblyName + "Module", true);
Then it creates the new helper class type with its fields:
TypeBuilder helperTypeBld = helperModule.DefineType(HandlerName +
"Helper", TypeAttributes.Public);
// Define fields
FieldBuilder typeField = helperTypeBld.DefineField("eventType",
typeof(Type), FieldAttributes.Private);
FieldBuilder eventField = helperTypeBld.DefineField("CommonEvent",
typeof(CommonEventHandlerDlgt),
FieldAttributes.Private);
EventBuilder commonEvent = helperTypeBld.DefineEvent("CommonEvent",
EventAttributes.None,
typeof(CommonEventHandlerDlgt));
Now, the constructor method can be created:
Type objType = Type.GetType("System.Object");
ConstructorInfo objCtor = objType.GetConstructor(new Type[0]);
// Build constructor with arguments (Type)
Type[] ctorParams = new Type[] { typeof(EventInfo) };
ConstructorBuilder ctor = helperTypeBld.DefineConstructor(MethodAttributes.Public,
CallingConventions.Standard, ctorParams);
And finally the code for the constructor is emitted through the ILGenerator
class:
// Call constructor of base class
ILGenerator ctorIL = ctor.GetILGenerator();
ctorIL.Emit(OpCodes.Ldarg_0);
ctorIL.Emit(OpCodes.Call, objCtor);
// store first argument to typeField
ctorIL.Emit(OpCodes.Ldarg_0);
ctorIL.Emit(OpCodes.Ldarg_1);
ctorIL.Emit(OpCodes.Stfld, typeField);
// return
ctorIL.Emit(OpCodes.Ret);
As it can be seen, these are just a few IL statements which make the constructor code. The code for generating the typed event handler is a bit longer and can be inspected in the provided source code.
The Demo Solution
Besides the source code of the CommonEventHandler library, you can download a demo solution, which consists of three projects:
- CommonEventHandler: The common event handler library.
- CommonEventHandlerTest: A test project, which shows how to use the library in a typical scenario.
- UnknownAssembly: An assembly with a class, which emits some events of unknown type.
When running CommonEventHandlerTest.exe, make sure, that the UnknownAssembly.dll lies in the same directory as the executable. In the test project “CommonEventHandlerTest”, a method TestDllEvents()
loads the assembly “UnknownAssembly.dll” at run time. We assume, that we only know, that this unknown assembly must have a class with a method named “EmitEvents()
”. We do not need to know, how many events of which types are emitted by that class. Using reflection, all types contained in the DLL are parsed until a class with a method “EmitEvents()
” is found:
foreach (Type type in unknownAssembly.GetTypes())
{
MethodInfo mi = type.GetMethod("EmitEvents");
if (mi != null)
{
// We have found the class we were looking for!
…
}
}
The type is constructed …
// First, instantiate the class
// Get the default constructor ...
ConstructorInfo ci = type.GetConstructor(BindingFlags.Instance |
BindingFlags.Public,
null, CallingConventions.HasThis,
new Type[0], null);
if (ci == null)
continue; // Do nothing, if no default constructor found
// ... and create the class by invoking the default constructor
object unknownClass = ci.Invoke(new object[0]);
and our EventHandlerFactory
too …
// Then, create the Event Handler Factory
EventHandlerFactory factory = new EventHandlerFactory("EventHandlerTest");
Now, we are ready to read out the events which are defined in our unknown class and create specific event handlers for them. Using the Delegate
class, we create a delegate for each created event handler helper and link that delegate to the event.
// So, now search the events, that class can emit
EventInfo[] events = type.GetEvents();
// and create a customized event handler for them ...
foreach (EventInfo info in events)
{
// Create the event handler
object eventHandler = factory.GetEventHandler(info);
// Create a delegate, which points to the custom event handler
Delegate customEventDelegate =
Delegate.CreateDelegate(info.EventHandlerType, eventHandler,
"CustomEventHandler");
// Link event handler to event
info.AddEventHandler(unknownClass, customEventDelegate);
After that, we still have to link our own general event handler “MyEventCallback
“ to the general event, which is emitted by our helper class.
// Map our own event handler to the common event
EventInfo commonEventInfo =
eventHandler.GetType().GetEvent("CommonEvent");
Delegate commonDelegate =
Delegate.CreateDelegate(commonEventInfo.EventHandlerType,
this, "MyEventCallback");
commonEventInfo.AddEventHandler(eventHandler, commonDelegate);
}
Now, everything is prepared to call the “EmitEvents
” method of the UnknownAssembly
and look, if our general event handler “MyEventCallback
” is properly called.
// We can now call the 'EmitEvent' method with no arguments
mi.Invoke(unknownClass, new object[0]);
Conclusion
The CommonEventHandler library helps us in dealing with arbitrary events. It is no longer necessary to provide a corresponding event handler method at compile time. Instead, it is possible to create the necessary event handler class at run time and route the event to a common, well defined event handler. The provided code can certainly be optimized. Comments are welcome. Have fun!