views:

259

answers:

2

Hey,

I have a function that receives two parameters - an object, and an EventInfo structure defining an event on that object. I need to block that function until the specified event fires. The problem I have is, how do I add a delegate to the specified event, when the type of handler could be anything? Note that I don't care about the parameters of that resulting event call, I just need to catch the fact it is raised.

I have already tried using EventInfo.AddEventHandler to add a really general delegate type (EventHandler), but to no avail. I have also tried the same, but using Activator to create an instance of the type specified in the EventInfo.EventHandlerType property, but no joy.

Alternatively, if anyone has a way to do similar, given an object, and the name of an event on that object, then that would work also.

I am using C# and .NET 2.0.

Cheers

A: 

I don't fully understand the question, maybe a code example might have been better. But one think you can do is add a parameterless delegate as the eventhandler. This will work regardless of the delegate type for the event defined.

someobject.someevent += delegate{ // do whatever;}
Pratik
Cheers for the suggestion, but I'm afraid that I don't actually have access to the event, only to the EventInfo structure describing it.
Kazar
+1  A: 

A hint to the solution can be use of MethodBuilder class. Using it you can generate a method at runtime that fits the delegate the EventInfo expects.

Example based on it (Many optimizations can be done but it works for most cases):

namespace AutoEventListener
{
    using System;
    using System.Linq;
    using System.Collections.Generic;
    using System.Reflection;
    using System.Reflection.Emit;

    public class EventExample
    {
        public static event EventHandler MyEvent;

        public void Test()
        {
            bool called;
            var eventInfo = GetType().GetEvent("MyEvent");
            EventFireNotifier.GenerateHandlerNorifier(eventInfo,
                callbackEventInfo =>
                    {
                        called = true;
                    });

            MyEvent(null, null);;
        }
    }

    public class EventFireNotifier
    {
        static private readonly Dictionary<int, EventInfo> eventsMap = new Dictionary<int, EventInfo>();
        static private readonly Dictionary<int, Action<EventInfo>> actionsMap = new Dictionary<int, Action<EventInfo>>();
        static private int lastIndexUsed;
        public static MethodInfo GenerateHandlerNorifier(EventInfo eventInfo, Action<EventInfo> action)
        {
            MethodInfo method = eventInfo.EventHandlerType.GetMethod("Invoke");
            AppDomain myDomain = AppDomain.CurrentDomain;
            var asmName = new AssemblyName(){Name = "HandlersDynamicAssembly"};

            AssemblyBuilder myAsmBuilder = myDomain.DefineDynamicAssembly(
                asmName,
                AssemblyBuilderAccess.RunAndSave);

            ModuleBuilder myModule = myAsmBuilder.DefineDynamicModule("DynamicHandlersModule");

            TypeBuilder typeBuilder = myModule.DefineType("EventHandlersContainer", TypeAttributes.Public);

            var eventIndex = ++lastIndexUsed;
            eventsMap.Add(eventIndex, eventInfo);
            actionsMap.Add(eventIndex, action);

            var handlerName = "HandlerNotifierMethod" + eventIndex;

            var parameterTypes = method.GetParameters().Select(info => info.ParameterType).ToArray();
            AddMethodDynamically(typeBuilder, handlerName, parameterTypes, method.ReturnType, eventIndex);

            Type type = typeBuilder.CreateType();

            MethodInfo notifier = type.GetMethod(handlerName);

            var handlerDelegate = Delegate.CreateDelegate(eventInfo.EventHandlerType, notifier);

            eventInfo.AddEventHandler(null, handlerDelegate);
            return notifier;
        }

        public static void AddMethodDynamically(TypeBuilder myTypeBld, string mthdName, Type[] mthdParams, Type returnType, int eventIndex)
        {
            MethodBuilder myMthdBld = myTypeBld.DefineMethod(
                                                 mthdName,
                                                 MethodAttributes.Public |
                                                 MethodAttributes.Static,
                                                 returnType,
                                                 mthdParams);

            ILGenerator generator = myMthdBld.GetILGenerator();

            generator.Emit(OpCodes.Ldc_I4, eventIndex);
            generator.EmitCall(OpCodes.Call, typeof(EventFireNotifier).GetMethod("Notifier"), null);
            generator.Emit(OpCodes.Ret);
        }

        public static void Notifier(int eventIndex)
        {
            var eventInfo = eventsMap[eventIndex];
            actionsMap[eventIndex].DynamicInvoke(eventInfo);
        }
    }
}

The class EventFireNotifier register for EventInfo an Action which is called when the event is fired.

I hope it helps.

Elisha
The method appears to create a dll for every method you create, could that not slow it down somewhat?
Kazar
I think the slow part is creating the method (uses reflection) but once it's compiled I think it should have fine performance (but I never did it so I can't tell for sure...).
Elisha
That does look like good code (even though it is using var, which I cannot), however I found a simpler workaround to my problem, by requiring the top level initialisation code to provide delegates for each event handler type.
Kazar