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;
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);
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]);
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:
ILGenerator ctorIL = ctor.GetILGenerator();
ctorIL.Emit(OpCodes.Ldarg_0);
ctorIL.Emit(OpCodes.Call, objCtor);
ctorIL.Emit(OpCodes.Ldarg_0);
ctorIL.Emit(OpCodes.Ldarg_1);
ctorIL.Emit(OpCodes.Stfld, typeField);
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)
{
�
}
}
The type is constructed �
ConstructorInfo ci = type.GetConstructor(BindingFlags.Instance |
BindingFlags.Public,
null, CallingConventions.HasThis,
new Type[0], null);
if (ci == null)
continue;
object unknownClass = ci.Invoke(new object[0]);
and our EventHandlerFactory
too �
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.
EventInfo[] events = type.GetEvents();
foreach (EventInfo info in events)
{
object eventHandler = factory.GetEventHandler(info);
Delegate customEventDelegate =
Delegate.CreateDelegate(info.EventHandlerType, eventHandler,
"CustomEventHandler");
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.
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.
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!