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(2 番目の引数は、任意のオブジェクト インスタンスではなく、値の型の型である必要があります)。

代わりに、ldtoken(Emitすでに使用しているオーバーロードを使用して、アクションのメソッドを 2 番目の引数として渡します) を使用し、その後に を呼び出し、MethodBase.GetMethodFromHandle次にExecuteEventメソッドを使用する必要があります。Emit(OpCodes.Call, exec)here と notのような呼び出しを使用する必要があることに注意してくださいEmitCall。そのドキュメントでは、通常の呼び出しではなく、varargs メソッドの呼び出しのみを目的としていることを明示的に示しています。これは、非ジェネリック メソッドへのデリゲートで機能するはずです。ジェネリック メソッドの場合は、追加のフープをジャンプする必要があります。

これにより、IL 生成に関する最も明白な問題が解消されるはずです。ただし、あなたのアプローチには少なくとも 1 つの概念的な問題があると思います:Actionデリゲートが非静的メソッドを指している場合はどうなりますか? 次に、アクションの もキャプチャして使用する必要がありますがTarget、これは重要です。

于 2013-03-28T22:12:32.770 に答える