Reflection: Event handling

snarfblam

Ultimate Contributor
Joined
Jun 10, 2003
Messages
2,097
Location
USA
Almost any aspect of any object can be handled dynamically through reflection. Functions can be called dynamically because we can pass parameters in an array. We can access properties and fields and instantiate classes. We can discover base classes and interfaces. But is there a way to dynamically handle events? To receive parameters as an array from an event in the same way that we would pass them to a function using reflection, or anything to that effect?

I'd like to avoid is compiling and creating assembies during runtime. It seems as though it could get pretty bulky if I need to handle a very large range of event types. I will need to create them as I need them, which means that if I have 50 event types I would need to emit 50 dynamic assemblies. I suppose I could optimize by creating and saving an assembly that contains all encountered event types when the app closes (basically caching them all in a single DLL) so that next time I will have far fewer assemblies, but it still seems to be a bit much.
 
Heh heh, a few hours too late, but I may as well show the fruits of my labour.

After reading this interesting question, I pondered about it for a while and a hairbrained solution congealed in my mind. A quick glance at the code project effort shows that it does what you wanted to avoid - dynamic code generation. My solution offers a different approach, with one caveat which I will explain afterwards.

Basically, I implemented a series of methods which take between zero and three arguments (though obviously more could be implemented) all of type object. Thus, relying on the delegate contravariance introduced in C# 2.0, we can create instances of the delegate type used for the event handler, and add it to the event, without needing to know the exact delegate signature.

If that all makes sense, here is the code. First up, the event sources:

C#:
    // Pointless classes which just raise events
    class RaisesEvents
    {
        public delegate void EventZeroHandler();
        public delegate void EventOneHandler(string one);
        public delegate void EventTwoHandler(RaisesEvents one, EventArgs two);
        public delegate void EventThreeHandler(string one, List<int> two, double[] three);

        public event EventZeroHandler EventZero;
        public event EventOneHandler EventOne;
        public event EventTwoHandler EventTwo;
        public event EventThreeHandler EventThree;

        public void RaiseThemAll()
        {
            try {
                EventZero();
                EventOne("One");
                EventTwo(this, new EventArgs());
                EventThree("One", new List<int>(2), new double[] {3.0f});
            } catch {}
        }
    }

    class RaisesEventsToo
    {
        public delegate void EventZeroHandler();
        public delegate void EventOneHandler(string one);
        public delegate void EventTwoHandler(RaisesEventsToo one, EventArgs two);
        public delegate void EventThreeHandler(string one, List<int> two, double[] three);

        public event EventZeroHandler EventZero;
        public event EventOneHandler EventOne;
        public event EventTwoHandler EventTwo;
        public event EventThreeHandler EventThree;

        public void RaiseThemAll()
        {
            try {
                EventZero();
                EventOne("One");
                EventTwo(this, new EventArgs());
                EventThree("One", new List<int>(2), new double[] { 3.0f });
            } catch { }
        }
    }

Then, our class which can attach itself to the events of an object:

C#:
    class EventAttacher
    {


        static void Main(string[] args)
        {
            EventAttacher p = new EventAttacher();
            RaisesEvents re = new RaisesEvents();
            RaisesEventsToo re2 = new RaisesEventsToo();

            p.AttachToEvents(re);
            p.AttachToEvents(re2);
            re.RaiseThemAll();
            re2.RaiseThemAll();

            Console.WriteLine("Done");
            Console.ReadLine();
        }


        public void HandlerZero()
        {
            Console.WriteLine("Received no parameters");
        }
        public void HandlerOne(object one)
        {
            Console.WriteLine("Received one parameter: " + one.GetType().Name);
        }
        public void HandlerTwo(object one, object two)
        {
            Console.WriteLine("Received two parameters: " + one.GetType().Name + "; " + two.GetType().Name);
        }
        public void HandlerThree(object one, object two, object three)
        {
            Console.WriteLine("Received three parameters: " + one.GetType().Name + "; " +
                two.GetType().Name + "; " + three.GetType().Name);
        }



        public void AttachToEvents(object o)
        {
            Type            evtype;
            ConstructorInfo ctorinf;
            MethodInfo[]    methods;
            Delegate        dg;
            int             numargs;

            //Initialise method info
            methods = new MethodInfo[4];
            methods[0] = this.GetType().GetMethod("HandlerZero");
            methods[1] = this.GetType().GetMethod("HandlerOne");
            methods[2] = this.GetType().GetMethod("HandlerTwo");
            methods[3] = this.GetType().GetMethod("HandlerThree");

            //Loop through events of o and attach
            foreach (EventInfo ev in o.GetType().GetEvents()) {

                evtype = ev.EventHandlerType;

                //How many arguments?
                numargs = evtype.GetMethod("Invoke").GetParameters().Length;

                //Get delegate constructor
                ctorinf = evtype.GetConstructor(new Type[] {typeof(object), typeof(IntPtr)});

                //Create delegate with appropriate number of args
                dg = (Delegate) ctorinf.Invoke(new object[] {
                            this, methods[numargs].MethodHandle.GetFunctionPointer()
                       });

                //Assign the delegate to the event
                if (dg == null) {
                    Console.WriteLine("Failed to instantiate delegate for " + numargs + "-argument event");
                } else {
                    ev.AddEventHandler(o, dg);
                    Console.WriteLine("Attached to " + numargs + "-argument event");
                }
            }

        }

    }


Ugly! Basically, the AttachToEvents method goes through the events of the target object and creates a delegate with the appropriate number of arguments and attaches it. The output for this test case looks like so:

Code:
Attached to 0-argument event
Attached to 1-argument event
Attached to 2-argument event
Attached to 3-argument event
Attached to 0-argument event
Attached to 1-argument event
Attached to 2-argument event
Attached to 3-argument event
Received no parameters
Received one parameter: String
Received two parameters: RaisesEvents; EventArgs
Received three parameters: String; List`1; Double[]
Received no parameters
Received one parameter: String
Received two parameters: RaisesEventsToo; EventArgs
Received three parameters: String; List`1; Double[]
Done

Now, to the caveat - the events you attach to must only take reference type arguments. Value types do not work with this method.

Hope it all makes sense, and is of some use. :cool:
 
Last edited:
Well, like I said, I would prefer not to create an assembly for each type of event (both of the links do this), but I don't think it can be helped.

Mr Paul, each line of code is clearly commented but it is hard to make out the logic of the code as a whole. By the looks of things, though, I would say that when using your method, the event handlers must already be defined for each given number of arguments. Also, I think there would be a problem with your code when pass-by-reference arguments are involved. And value types are a must. Basically, I want to be able to handle just about anything that a user might try to link to dynamically. That means anything goes. Not just sender/eventargs events, but any kind of signature. Your code looks handy and dynamic, but it doesn't quite fit the bill.
 
I don't think you need to have separate assemblies. You just use the AssemblyBuilder class:
C#:
AssemblyName assemblyName = new AssemblyName();
assemblyName.Name = "DynamicEvents";
AssemblyBuilder ab = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
You can then call the "DefineDynamicModule" on the AssemblyBuilder instance and start building as many types (dynamically) as you like.
 
marble_eater said:
Mr Paul, each line of code is clearly commented but it is hard to make out the logic of the code as a whole. By the looks of things, though, I would say that when using your method, the event handlers must already be defined for each given number of arguments. Also, I think there would be a problem with your code when pass-by-reference arguments are involved. And value types are a must. Basically, I want to be able to handle just about anything that a user might try to link to dynamically. That means anything goes. Not just sender/eventargs events, but any kind of signature. Your code looks handy and dynamic, but it doesn't quite fit the bill.

Yes, you are correct in that every event handler method must be explicitly declared, so if you predict you might need to handle events taking up to 20 parameters, then you'd need to define 21 handlers, which is not ideal. And you quite rightly note that it would not work with ref or out parameters, or value types, so its use is extremely limited.

Although it will clearly not be that helpful to you, I might as well explain what it is doing. Basically it finds the number of arguments the event takes by counting the parameters of the Invoke method of the event's delegate, which always matches the delegate's signature. It then selects one of our predefined handler methods with a matching number of arguments, and creates a Delegate object from that method. Every delegate has a constructor which takes an object and an IntPtr (to the method body). We pass in the object containing the method (current instance of EventAttacher) and the pointer to the appropriate method, which can be retrieved from the MethodInfo. The constructed delegate is then attached to the event.

Good luck :cool:
 
MrPaul said:
Yes, you are correct in that every event handler method must be explicitly declared, so if you predict you might need to handle events taking up to 20 parameters, then you'd need to define 21 handlers, which is not ideal. And you quite rightly note that it would not work with ref or out parameters, or value types, so its use is extremely limited.
Not true.

Here's some code that will dynamically create a new type that is subscribed to another type's events:
C#:
static Type BuildDyanmicType(AssemblyBuilder ab, string assemblyName, Type sourceType)
{
    ModuleBuilder mb = ab.DefineDynamicModule(assemblyName);
    
    TypeBuilder tb = mb.DefineType(string.Format("{0}DynamicHandler", sourceType.Name), TypeAttributes.Class | TypeAttributes.Public);

    // build a method to handle each event in the source type
    foreach (EventInfo eventInfo in sourceType.GetEvents())
    {
        MethodInfo method = eventInfo.EventHandlerType.GetMethod("Invoke");

        List<Type> parameterTypes = new List<Type>();

        foreach (ParameterInfo parameter in method.GetParameters())
        {
            parameterTypes.Add(parameter.ParameterType);
        }

        MethodBuilder handler = tb.DefineMethod(string.Format("{0}_Handler", eventInfo.Name),
        MethodAttributes.Public | MethodAttributes.Static,
        method.ReturnType, parameterTypes.ToArray());

        ILGenerator il = handler.GetILGenerator();
        il.EmitWriteLine("The event has been raised.");
          
        il.Emit(OpCodes.Ret);
    }

    return tb.CreateType();
}
 
Yes that's all very nice, but I was explaining the construction of code I posted previously, as you may notice if you read the text I quoted. Of course your 'Dyanmic' types approach (or rather, the approach used at the code project) is superior.

:-\
 
Last edited:
Okay, let me make this clear. No offense, but no one seems to be reading the whole thread.

Using dynamic assemblies means that I need to create new assemblies at run-time. Since I will need to create and access handlers immediately as I need them I will need to finalize each and every dynamic delegate I create, in its own assembly, hence an assembly for each type of event. This is what I want to avoid. You can't go back and amend a dynamic assembly once the process is finished, and I will need to finish the process for every delegate type because it needs to be created and used as soon as it is defined (I have no way of knowing ahead of time which types of delegates I will need).

MrPaul came up with an ingenious bit of code that can use pre-defined delegates and handlers to handle 95% of the events I will run into, but 95% isn't enough.

I think that a hybrid solution would be my best bet. Predefine the very common event handlers. For the rest, create dynamic assemblies. But, on application close, create a single new dynamic assembly where I can consolidate all dynamically created event handlers, and save this to a DLL. Next time I load the app I will have a single assembly that contains any previously-created handlers.

Correction:
MSDN said:
You can use the DynamicMethod class to generate and execute a method at run time, without having to generate a dynamic assembly and a dynamic type to contain the method. Dynamic methods are the most efficient way to generate and execute small amounts of code.
 
Last edited:
Back
Top