Click here to Skip to main content
Click here to Skip to main content
Go to top

A Universal Event Handler Factory

, 2 Aug 2005
Rate this:
Please Sign up or sign in to vote.
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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Ulrich Proeller
Web Developer
Germany Germany
First computer in 1977.
Since then, addicted to computers and programming.
Founder and CEO of the prosa GmbH, a german consulting company focused on .NET solutions.
Married, 4 adult children, living in Mering near Munich.

Comments and Discussions

 
SuggestionHooking the same event on multiple instances Pinmemberga5p0d312-Jun-13 11:51 
QuestionReturn value from the event handling methods PinmemberSalvador Gurrola1-Mar-12 8:51 
GeneralNice article. Just got the link and read it Pinmembersudhansu_k12322-Dec-11 22:30 
GeneralWindows Forms events PinmemberLyhr7-Aug-07 21:47 
QuestionEven more? PinmemberH. Spranger25-Jul-06 1:17 
AnswerRe: Even more? PinmemberUlrich Proeller25-Jul-06 1:32 
GeneralEvent Handler Parameters [modified] PinmemberIcingDeath27-Jun-06 4:25 
Info.EventHandlerType.GetMethod("Invoke").GetParameters
This would probably do it! Thanks Smile | :)
 
-- modified at 10:31 Tuesday 27th June, 2006
GeneralKudos PinmemberKevin James1-Nov-05 4:05 
GeneralGenerics Pinmembermwdiablo6-Oct-05 12:46 
Generalerror in remove event handler PinsussTim Cools1-Aug-05 3:54 
GeneralRe: error in remove event handler PinmemberTim Cools1-Aug-05 5:09 
GeneralRe: error in remove event handler PinmemberUlrich Proeller1-Aug-05 6:07 
Generalerror in remove event handler PinsussTim Cools1-Aug-05 3:53 
QuestionWhy do you need this? PinmemberBoudino14-Jul-05 0:46 
AnswerRe: Why do you need this? PinmemberUlrich Proeller14-Jul-05 1:25 
AnswerRe: Why do you need this? PinmemberHummerX16-Dec-07 9:30 
GeneralYour code helps out with unit tests PinmemberRoy Osherove13-Jun-05 6:59 
GeneralBeautifull PinmemberTutu6-Jun-05 13:28 
GeneralActually.. PinmemberFurty5-Jun-05 23:08 
GeneralRe: Actually.. PinprotectorMarc Clifton6-Jun-05 0:41 
GeneralRe: Actually.. PinmemberFurty6-Jun-05 2:36 
GeneralRe: Actually.. PinmemberUlrich Proeller6-Jun-05 3:01 
GeneralRe: Actually.. Pinmembertheahmed6-Jun-05 6:34 
GeneralRe: Actually.. PinprotectorMarc Clifton6-Jun-05 6:52 
GeneralRe: Actually.. PinmemberHummerX16-Dec-07 9:42 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140905.1 | Last Updated 2 Aug 2005
Article Copyright 2005 by Ulrich Proeller
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid