IL Emit for invoking a delegate instance?

前端 未结 2 383
花落未央
花落未央 2021-01-12 15:47

Basically, I\'m accepting an event name as a string, to get the EventInfo. Then, I\'m discovering the event handler type and event argument type using reflectio

相关标签:
2条回答
  • 2021-01-12 16:15

    OK - this might help; it generates the IL to switch between delegate types, as long as they match the standard pattern. It adds a castclass only when necessary (so if you are going from a MouseEventArgs to an EventArgs it isn't necessary, but in the reverse direction it is). Since you are clearly working with reflection, I haven't used generics (which would make things harder).

    The cheeky bit is that instead of using a capture class, it pretends the method belongs to the data I would capture, and uses the state as arg0. I can't decide if that makes it evil or clever, so I'll go with "clevil".

    using System;
    using System.Reflection;
    using System.Reflection.Emit;
    using System.Threading;
    using System.Windows.Forms;
    
    class Program {
        static ParameterInfo[] VerifyStandardHandler(Type type) {
            if (type == null) throw new ArgumentNullException("type");
            if (!typeof(Delegate).IsAssignableFrom(type)) throw new InvalidOperationException();
            MethodInfo sig = type.GetMethod("Invoke");
            if (sig.ReturnType != typeof(void)) throw new InvalidOperationException();
            ParameterInfo[] args = sig.GetParameters();
            if (args.Length != 2 || args[0].ParameterType != typeof(object)) throw new InvalidOperationException();
            if (!typeof(EventArgs).IsAssignableFrom(args[1].ParameterType)) throw new InvalidOperationException();
            return args;
        }
        static int methodIndex;
        static Delegate Wrap(Delegate value, Type type) {
            ParameterInfo[] destArgs = VerifyStandardHandler(type);
            if (value == null) return null; // trivial
            if (value.GetType() == type) return value; // already OK
            ParameterInfo[] sourceArgs = VerifyStandardHandler(value.GetType());
            string name = "_wrap" + Interlocked.Increment(ref methodIndex);
            Type[] paramTypes = new Type[destArgs.Length + 1];
            paramTypes[0] = value.GetType();
            for (int i = 0; i < destArgs.Length; i++) {
                paramTypes[i + 1] = destArgs[i].ParameterType;
            }
            DynamicMethod dyn = new DynamicMethod(name, null, paramTypes);
            MethodInfo invoker = paramTypes[0].GetMethod("Invoke");
            ILGenerator il = dyn.GetILGenerator();
            il.Emit(OpCodes.Ldarg_0);
            il.Emit(OpCodes.Ldarg_1);
            il.Emit(OpCodes.Ldarg_2);
            if (!sourceArgs[1].ParameterType.IsAssignableFrom(destArgs[1].ParameterType)) {
                il.Emit(OpCodes.Castclass, sourceArgs[1].ParameterType);
            }
            il.Emit(OpCodes.Call, invoker);
            il.Emit(OpCodes.Ret);
            return dyn.CreateDelegate(type, value);
        }
        static void Main() {
            EventHandler handler = delegate(object sender, EventArgs eventArgs) {
                Console.WriteLine(eventArgs.GetType().Name);
            };
            MouseEventHandler wrapper = (MouseEventHandler)Wrap(handler, typeof(MouseEventHandler));
            MouseEventArgs ma = new MouseEventArgs(MouseButtons.Left, 1, 1, 1, 1);
            wrapper(new object(), ma);
    
            EventHandler backAgain = (EventHandler)Wrap(wrapper, typeof(EventHandler));
            backAgain(new object(), ma);
        }
    }
    

    Obviously you still need to generate a delegate to the event using regular methods (Delegate.CreateDelegate etc), but you can then wrap it to an EventHandler, or the reverse.

    0 讨论(0)
  • 2021-01-12 16:23

    It turns out I was vastly over-complicating things! Barry Kelly had the right idea:

    static T CastDelegate<T>(Delegate src)
        where T : class
    {
        return (T)(object)Delegate.CreateDelegate(
            typeof(T),
            src.Target,
            src.Method,
            true); // throw on fail
    }
    

    That works for my test cases.

    0 讨论(0)
提交回复
热议问题