Search code examples
c#reflectioncildynamicmethodilgenerator

Make DynamicMethod call other method


I want to be able to subscribe to any event of any object by passing event's name and Action dependent on client code. I have following code

public static class EventSubscriber
{
    public static object Subscriber<TEventArgs>(
        this object obj,
        string eventName,
        Action handler,
        Func<TEventArgs, bool> canExecute)
    {
        var eventInfo = obj.GetType().
            GetEvent(eventName);

        if (eventInfo == null)
            throw new ArgumentException("Event name provided does not exist", nameof(eventName));

        var handlerArgs = eventInfo.EventHandlerType.
            GetMethod("Invoke").
            GetParameters()
            .Select(p => p.ParameterType).ToArray();



        var method = new DynamicMethod("method", typeof (void), handlerArgs);
        var generator = method.GetILGenerator(256);
        generator.EmitCall(OpCodes.Call, handler.Method, null);

        eventInfo.
            AddEventHandler(
                obj,
                method.CreateDelegate(eventInfo.EventHandlerType));
        return obj;
    }
}

Usage of code above :

var Polygons = new ObservableCollection<Polygon>(myList);
Polygons.Subscriber<NotifyCollectionChangedEventArgs>
              ("CollectionChanged",
              () => MessageBox.Show("hello"),
              e => e.OldItems != null);

It causes an InvalidProgramException when the event fires. I know this is a tricky one and i could simply subscribe using += but could anybody tell why my code crashes? I suppose something wrong with ILGenerator.Emit, any suggestions?


Solution

  • You forgot to return at the end of the DynamicMethod.

    var method = new DynamicMethod("method", typeof (void), handlerArgs);
    var generator = method.GetILGenerator(256);
    generator.EmitCall(OpCodes.Call, handler.Method, null);
    generator.Emit(OpCodes.Ret); //every method must have a return statement
    

    And the class that the compiler creates for the () => MessageBox.Show("hello") lambda is private.[reference]

    When you use a public static method in a public class instead it works.

    var Polygons = new ObservableCollection<Polygon>(myList);
    Polygons.Subscriber<NotifyCollectionChangedEventArgs>
        ("CollectionChanged",
        () => MessageBox.Show("hello"), //must in a public class and a public static method
        e => e.OldItems != null);