Question

I'm trying to have an action fired whenever an event does, ignoring the events parameters (at least for now.) I'm finding the event via reflection, then create a dynamic method matching the expected signature (there's no guarantee it's only Sender/EventArgs) and from there try to invoke the action.

/// <summary>
/// Execute an action when an event fires, ignoring it's parameters.
/// 'type' argument is element.GetType()
/// </summary>
bool TryAsEvent(Type type, UIElement element, string actionStr, Action act)
{
    try
    {
        //Get event info
        var eventInfo = type.GetEvent(actionStr); //Something like 'Click'
        var eventType = eventInfo.EventHandlerType;

        //Get parameters
        var methodInfo = eventType.GetMethod("Invoke");
        var parameterInfos = methodInfo.GetParameters();
        var paramTypes = parameterInfos.Select((info => info.ParameterType)).ToArray();

        //Create method
        var dynamicMethod = new DynamicMethod("", typeof(void), paramTypes);

        //Static method that will invoke the Action (from our parameter 'act')
        //Necessary because the action itself wasn't recognized as a static method
        // which caused an InvalidProgramException
        MethodInfo exec = typeof(ThisClass).GetMethod("ExecuteEvent");

        //Generate IL
        var il = dynamicMethod.GetILGenerator();
        il.DeclareLocal(typeof(MethodInfo));

        //MethodInfo miExecuteAction = act.Method;
        //Commented out some trial and failure stuff
        il.Emit(OpCodes.Ldobj, act.Method);
        //il.Emit(OpCodes.Stloc, lc);
        //il.Emit(OpCodes.Ldloc, lc);
        //il.Emit(OpCodes.Ldobj, act.Method);
        //il.Emit(OpCodes.Ldarg_0);
        //il.Emit(OpCodes.Pop);
        il.EmitCall(OpCodes.Call, exec, null);
        il.Emit(OpCodes.Ret);

        //Test the method (the event under test has a handler taking 2 args):
        //var act2 = dynamicMethod.CreateDelegate(eventType);
        //act2.DynamicInvoke(new object[]{null, null});

        //Add handler
        var handler = dynamicMethod.CreateDelegate(eventType);
        eventInfo.AddEventHandler(element, handler);

        return true;
    }
    catch
    {
        return false;
    }
}

public static void ExecuteEvent(MethodInfo i)
{
    i.Invoke(null, null);
}

Can anyone tell me how to achieve this?

UPDATE: Here's a concise VS11 project file mimicking the real scenario:

Download

Update (Fix):

public class Program
{
    public static void Main(string[] args)
    {
        var x = new Provider();

        new Program().TryAsEvent(
            x.GetType(),
            x,
            "Click",
            new Program().TestInstanceMethod);
            //Use this lambda instead to test a static action
            //() => Console.WriteLine("Action fired when event did."));

        x.Fire();
        Console.ReadLine();
    }

    public void TestInstanceMethod()
    {
        Console.WriteLine("Action fired when event did.");
    }

    /// <summary>
    /// Execute an action when an event fires, ignoring it's parameters.
    /// </summary>
    bool TryAsEvent(Type type, object element, string actionStr, Action act)
    {
        try
        {
            var getMFromH = typeof(MethodBase)
                .GetMethod("GetMethodFromHandle", 
                BindingFlags.Public | BindingFlags.Static, 
                null, 
                new[] { typeof(RuntimeMethodHandle) }, null);

            //Get event info
            var eventInfo = type.GetEvent(actionStr);
            var eventType = eventInfo.EventHandlerType;

            //Get parameters
            var methodInfo = eventType.GetMethod("Invoke");
            var parameterInfos = methodInfo.GetParameters();
            var paramTypes = parameterInfos.Select((info => info.ParameterType)).ToArray();

            //Non-static action?
            var target = act.Target;
            if (target != null) //Prepend instance object
                paramTypes = new[] {target.GetType()}.Union(paramTypes).ToArray();

            //Create method
            var dynamicMethod = new DynamicMethod("", typeof(void), paramTypes);

            //Static method that will invoke the Action (from our parameter 'act')
            var exec = typeof (Program).GetMethod
                (target != null // Call proper method based on scope of action
                     ? "ExecuteEvent"
                     : "ExecuteEventStati");

            //Generate IL
            var il = dynamicMethod.GetILGenerator();
            if (target != null) //Push object instance on stack if working with non-static action
                il.Emit(OpCodes.Ldarg_0);
            il.Emit(OpCodes.Ldtoken, act.Method);
            il.Emit(OpCodes.Call, getMFromH);
            il.Emit(OpCodes.Call, exec);
            il.Emit(OpCodes.Ret);

            //Add handler
            var handler =
                target != null
                //Call with target obj if we're working with a non-static action
                ? dynamicMethod.CreateDelegate(eventType, target)
                : dynamicMethod.CreateDelegate(eventType);
            eventInfo.AddEventHandler(element, handler);

            return true;
        }
        catch (Exception ex)
        {
            Console.WriteLine("Exception: " + ex);
            return false;
        }
    }

    public static void ExecuteEventStati(MethodInfo i)
    {
        i.Invoke(null, null);
    }
    public static void ExecuteEvent(object o, MethodInfo i)
    {
        i.Invoke(o, null);
    }
}

And here is the unrelated code for that example (in case anyone wants to copy & paste):

public class Provider
{
    public event MyRoutedEventHandler Click;

    public void Fire()
    {
        if (Click != null)
            Click(this, new MyRoutedEventArgs());
    }
}

public delegate void MyRoutedEventHandler(object sender, MyRoutedEventArgs e);

public class MyRoutedEventArgs : RoutedEventArgs
{
    public MyRoutedEventArgs()
    {
    }

    public MyRoutedEventArgs(RoutedEvent routedEvent)
        : this(routedEvent, (object)null)
    {
    }

    public MyRoutedEventArgs(RoutedEvent routedEvent, object source)
        : base(routedEvent, source){}
}
Était-ce utile?

La solution

You can't use ldobj like that. ldobj is supposed to take an address or managed reference from the stack and box a value type stored at that address into an object. Instead, you're calling ldobj with an empty stack and you're using the wrong overload of Emit (the second argument should be the type of the value type, not an arbitrary object instance).

Instead, you should be using ldtoken (with the Emit overload you're already using, passing in the action's method as the second argument), followed by calls to MethodBase.GetMethodFromHandle and then your ExecuteEvent method. Note that you should use a call like Emit(OpCodes.Call, exec) here and not EmitCall, whose documentation explicitly indicates that it is intended only for calling varargs methods and not for normal calls. That should work for delegates to non-generic methods - for generic methods you need to jump through some extra hoops.

That should clear up the most obvious problems with the IL generation. However, I think there is at least one conceptual problem with your approach: what if the Action delegate points to a non-static method? Then you need to capture and use the action's Target, too, which will be non-trivial.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top