Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

A Universal Event Handler Factory

0.00/5 (No votes)
2 Aug 2005 1  
A factory, which dynamically creates helper classes to hook on events of any signature.

Sample Image - CommonEventHandler.jpg

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!

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here