2

我试图在事件发生时触发一个动作,忽略事件参数(至少现在)。我通过反射找到事件,然后创建一个匹配预期签名的动态方法(不能保证它只是发件人/EventArgs) 并从那里尝试调用该操作。

/// <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);
}

谁能告诉我如何实现这一目标?

更新:这是一个模仿真实场景的简洁 VS11 项目文件:

下载

更新(修复):

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);
    }
}

这是该示例的无关代码(以防有人想要复制和粘贴):

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){}
}
4

1 回答 1

3

你不能这样用ldobjldobj应该从堆栈中获取地址或托管引用,并将存储在该地址的值类型装箱到对象中。相反,您使用ldobj空堆栈调用并且使用了错误的重载Emit(第二个参数应该是值类型的类型,而不是任意对象实例)。

相反,您应该使用ldtoken(使用Emit您已经在使用的重载,将操作的方法作为第二个参数传递),然后调用MethodBase.GetMethodFromHandle您的ExecuteEvent方法。请注意,您应该使用类似Emit(OpCodes.Call, exec)here 而不是的调用EmitCall,其文档明确指出它仅用于调用 varargs 方法,而不用于正常调用。这应该适用于非泛型方法的委托 - 对于泛型方法,您需要跳过一些额外的障碍。

这应该可以清除 IL 生成中最明显的问题。但是,我认为您的方法至少存在一个概念问题:如果Action委托指向非静态方法怎么办?然后,您还需要捕获和使用动作的Target,这将是非常重要的。

于 2013-03-28T22:12:32.770 回答