Click here to Skip to main content
15,867,453 members
Articles / Programming Languages / C#
Article

A Universal Event Handler Factory

Rate me:
Please Sign up or sign in to vote.
4.83/5 (20 votes)
2 Aug 2005CPOL4 min read 127.7K   1.7K   86   29
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:

C#
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:

C#
public delegate void MyEventDelegate(int Counter, string Name, DateTime Time);

the C# representation of the generated helper class would look like:

C#
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.

C#
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:

C#
TypeBuilder helperTypeBld = helperModule.DefineType(HandlerName + 
                                "Helper", TypeAttributes.Public);
C#
// 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:

C#
Type objType = Type.GetType("System.Object"); 
ConstructorInfo objCtor = objType.GetConstructor(new Type[0]);
C#
// 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:

C#
// 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);
C#
// 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:

C#
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 …

C#
// 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 …

C#
// 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.

C#
// 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);
C#
 // 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.

C#
       // 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.

C#
// 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)


Written By
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 Pin
ga5p0d312-Jun-13 11:51
ga5p0d312-Jun-13 11:51 
QuestionReturn value from the event handling methods Pin
Salvador Gurrola1-Mar-12 8:51
Salvador Gurrola1-Mar-12 8:51 
GeneralNice article. Just got the link and read it Pin
sudhansu_k12322-Dec-11 22:30
sudhansu_k12322-Dec-11 22:30 
GeneralWindows Forms events Pin
Lyhr7-Aug-07 21:47
Lyhr7-Aug-07 21:47 
QuestionEven more? Pin
H. Spranger25-Jul-06 1:17
H. Spranger25-Jul-06 1:17 
AnswerRe: Even more? Pin
Ulrich Proeller25-Jul-06 1:32
Ulrich Proeller25-Jul-06 1:32 
GeneralEvent Handler Parameters [modified] Pin
John Korres27-Jun-06 4:25
John Korres27-Jun-06 4:25 
GeneralKudos Pin
Kevin James1-Nov-05 4:05
Kevin James1-Nov-05 4:05 
GeneralGenerics Pin
KiwiPiet6-Oct-05 12:46
KiwiPiet6-Oct-05 12:46 
Generalerror in remove event handler Pin
Tim Cools1-Aug-05 3:54
Tim Cools1-Aug-05 3:54 
GeneralRe: error in remove event handler Pin
Tim Cools1-Aug-05 5:09
Tim Cools1-Aug-05 5:09 
GeneralRe: error in remove event handler Pin
Ulrich Proeller1-Aug-05 6:07
Ulrich Proeller1-Aug-05 6:07 
Generalerror in remove event handler Pin
Tim Cools1-Aug-05 3:53
Tim Cools1-Aug-05 3:53 
hi,

first of all many thanks for posting this code. it helps me a lot to bypassing that it is not allowed to subscribe to events with derived signatures in .NET 1.1.

But i think i found a bug in the code. When i try to remove the event handler from the on the fly created object by placing

commonEventInfo.RemoveEventHandler(eventHandler, commonDelegate);

after

commonEventInfo.AddEventHandler(eventHandler, commonDelegate);

in the test application i receive the following exception:

Cannot remove the event handler since no public remove method exists for the event.

I don't know anything about IL and have currently no time get into is. Is there a solution for this problem?

Regards,

Tim
QuestionWhy do you need this? Pin
Boudino14-Jul-05 0:46
Boudino14-Jul-05 0:46 
AnswerRe: Why do you need this? Pin
Ulrich Proeller14-Jul-05 1:25
Ulrich Proeller14-Jul-05 1:25 
AnswerRe: Why do you need this? Pin
HummerX16-Dec-07 9:30
HummerX16-Dec-07 9:30 
GeneralYour code helps out with unit tests Pin
Roy Osherove13-Jun-05 6:59
Roy Osherove13-Jun-05 6:59 
GeneralBeautifull Pin
Corneliu Tusnea6-Jun-05 13:28
Corneliu Tusnea6-Jun-05 13:28 
GeneralActually.. Pin
Furty5-Jun-05 23:08
Furty5-Jun-05 23:08 
GeneralRe: Actually.. Pin
Marc Clifton6-Jun-05 0:41
mvaMarc Clifton6-Jun-05 0:41 
GeneralRe: Actually.. Pin
Furty6-Jun-05 2:36
Furty6-Jun-05 2:36 
GeneralRe: Actually.. Pin
Ulrich Proeller6-Jun-05 3:01
Ulrich Proeller6-Jun-05 3:01 
GeneralRe: Actually.. Pin
theahmed6-Jun-05 6:34
theahmed6-Jun-05 6:34 
GeneralRe: Actually.. Pin
Marc Clifton6-Jun-05 6:52
mvaMarc Clifton6-Jun-05 6:52 
GeneralRe: Actually.. Pin
HummerX16-Dec-07 9:42
HummerX16-Dec-07 9:42 

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

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