Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to attach event handler to an event using reflection?

I know about EventInfo.AddEventHandler(...) method which can be used to attach handler to an event. But what should be done if i can not even define proper signature of the event handler, as in, i don't even have reference to the event args expected by the handler?

I will explain the problem with the proper code.

// Scenario when I have everything available in my solution, Zero Reflection Scenario.

internal class SendCommentsManager
{
    public void Customize(IRFQWindowManager rfqWindowManager)
    {
        rfqWindowManager.SendComment += HandleRfqSendComment;
    }

    private void HandleRfqSendComment(object sender, SendCommentEventArgs args)
    {
        args.Cancel = true;
    }
}

Now, I want to achieve the same objective by using reflection. I have been able to figure out most of it but when i attach a delegate to the event (using AddEventHandler) it throws "Error binding to target method." exception.

I understand the reason behind this exception, attaching a wrong delegate to an event. But there must be some way to achieve this.

 internal class SendCommentsManagerUsingReflection
 {
     public void Customize(IRFQWindowManager rfqWindowManager)
     {
         EventInfo eventInfo = rfqWindowManager.GetType().GetEvent("SendComment");
         eventInfo.AddEventHandler(rfqWindowManager, 
             Delegate.CreateDelegate(eventInfo.EventHandlerType, this, "HandleRfqSendComment"));
         //<<<<<<<<<<ABOVE LINE IS WHERE I AM GOING WRONG>>>>>>>>>>>>>>
     }

     private void HandleRfqSendComment(object sender, object args)
     {
         Type sendCommentArgsType = args.GetType();
         PropertyInfo cancelProperty = sendCommentArgsType.GetProperty("Cancel");
         cancelProperty.SetValue(args, true, null);
     }
 }
like image 700
Manish Basantani Avatar asked Jun 25 '10 18:06

Manish Basantani


2 Answers

I think your code is failing because the HandleRfqSendComment is private. Instead you could directly create a delegate to that method, without passing its name to CreateDelegate. You would then need to convert the delegate to the required type, using the following method :

public static Delegate ConvertDelegate(Delegate originalDelegate, Type targetDelegateType)
{
    return Delegate.CreateDelegate(
        targetDelegateType,
        originalDelegate.Target,
        originalDelegate.Method);
}

In your code, you could use this method as follows :

EventInfo eventInfo = rfqWindowManager.GetType().GetEvent("SendComment");
Action<object, object> handler = HandleRfqSendComment;
Delegate convertedHandler = ConvertDelegate(handler, eventInfo.EventHandlerType);
eventInfo.AddEventHandler(rfqWindowManager, convertedHandler);
like image 189
Thomas Levesque Avatar answered Oct 05 '22 17:10

Thomas Levesque


A small addition to the already awesome answers here. Here's a helper class you could use to subscribe to events with actions.

public static partial class ReactiveExtensions
{
    public static EventHandler<TEvent> CreateGenericHandler<TEvent>(object target, MethodInfo method)
    {
        return (EventHandler<TEvent>)Delegate
            .CreateDelegate(typeof(EventHandler<TEvent>), 
            target, method);
    }

    public static EventHandler CreateHandler(object target, MethodInfo method)
    {
        return (EventHandler)Delegate
            .CreateDelegate(typeof(EventHandler),
            target, method);
    }
        
        
    static void BindEventToAction(object target, EventInfo eventInfo, Delegate action)
    {
        MethodInfo method;

        if (eventInfo.EventHandlerType.IsGenericType)
        {
            method = typeof(ReactiveExtensions)
                .GetMethod(nameof(CreateGenericHandler))
                .MakeGenericMethod(
                eventInfo.EventHandlerType.GetGenericArguments());
        }
        else
        {
            method = typeof(ReactiveExtensions)
                .GetMethod(nameof(CreateHandler));
        }


        Delegate @delegate = (Delegate)method.Invoke(null,
            new object[] { action.Target, action.Method });


        eventInfo.AddEventHandler(target, @delegate);
    }
}

Here's a sample on how to use this:

public static partial class ReactiveExtensions
{
    public static void Subscribe<T>(T source, string eventName)
    {
        EventInfo eventInfo = typeof(T).GetEvent(eventName);

        Action<object, object> action = (s, e) =>
        {
            Console.WriteLine("Event Called");
        };

        BindEventToAction(source, eventInfo, action);
    }
}
like image 29
Prince Owen Avatar answered Oct 05 '22 16:10

Prince Owen