用 Emit 动态调用 Func<T, object[], object>

Dynamically call Func<T, object[], object> with Emit

我正在尝试动态创建函数,在执行时只调用给定的 Func

 public IProxifier<T> Override(string method, Func<T, object[], object> handler)
    {
        if (!overr.ContainsKey(method))
        {
            Ops op = new Ops();
            op.GenericTypes = new Type[] { typeof(T) };
            op.MethodInfo = handler.GetMethodInfo();

            MethodBuilder mb = tb.DefineMethod(method, MethodAttributes.Public | MethodAttributes.ReuseSlot |
                    MethodAttributes.Virtual | MethodAttributes.HideBySig, typeof(object), new Type[] { typeof(object[]) });

            ILGenerator il = mb.GetILGenerator();


            //il.EmitCall(OpCodes.Callvirt, op.MethodInfo, op.GenericTypes);
            il.Emit(OpCodes.Ldarg_0);
            il.Emit(OpCodes.Callvirt, handler.GetMethodInfo());
            il.Emit(OpCodes.Ret);
            overr.Add(method, op);
        }
        return this;

    }

我正在使用反射创建一个动态类型,每次调用这个 Override 方法时,我都需要在这个动态创建的对象中创建给定的方法(覆盖先前存在的方法,即 ToString() )。

我已经尝试过使用 Emit 和 EmitCall 的各种方式,但我得到的只是 InvalidProgramException 或什么都没有。

我想要实现的是:

第一个问题 是当您在 IL 中调用方法时,它总是将调用方法的第一个参数对象作为第一个参数对象。对于静态方法,此参数为 null 但它仍应存在。所以第一个修复是加载 null 以在其他参数之前堆叠:

il.Emit(OpCodes.Ldnull); // method object parameter
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Callvirt, handler.GetMethodInfo());
il.Emit(OpCodes.Ret);

第二个问题是,根据handler类型,你试图调用需要堆栈中Tobject[]类型参数的方法在你打电话之前。目前您只加载 object[] 类型的第二个参数,因此您还应该使用 T.

类型的参数

根据你想做的事情,你可以用不同的方式解决它:

最简单的选项是在生成的方法中添加参数T

MethodBuilder mb = tb.DefineMethod(method, MethodAttributes.Public | 
    MethodAttributes.ReuseSlot | MethodAttributes.Virtual | MethodAttributes.HideBySig, 
    typeof(object), new Type[] { typeof(T), typeof(object[]) });

然后使用它的值调用 Func:

il.Emit(OpCodes.Ldnull);  // method object parameter
il.Emit(OpCodes.Ldarg_0); // first parameter of type T
il.Emit(OpCodes.Ldarg_1); // second parameter of type object[]
il.Emit(OpCodes.Callvirt, handler.GetMethodInfo());
il.Emit(OpCodes.Ret);

如果您无法更改动态方法签名,您可以从 Func 参数中删除 T 和将它作为另一个参数传递给 Override 方法:

public IProxifier<T> Override(string method, T someName, Func<object[], object> handler)

如果您也无法更改 Func 的签名,但您没有在其中使用 T 的值,你可以只加载默认值到堆栈。通过加载 null:

可以很容易地为引用类型完成
il.Emit(OpCodes.Ldnull);  // method object parameter
il.Emit(OpCodes.Ldnull);  // first parameter of type T
il.Emit(OpCodes.Ldarg_1); // second parameter of type object[]
il.Emit(OpCodes.Callvirt, handler.GetMethodInfo());
il.Emit(OpCodes.Ret);

但是对于值类型就有点棘手了,你应该用这个类型声明局部变量初始化它,然后调用initobj指令创建值,然后将它加载到堆栈中:

il.Emit(OpCodes.Ldloca_S, generator.DeclareLocal(typeof(T)); //create local variable
il.Emit(OpCodes.Initobj, typeof(T)); //initialize it with default value

il.Emit(OpCodes.Ldnull);  // method object parameter
il.Emit(OpCodes.Ldloc_0); // first parameter of type T from local variable
il.Emit(OpCodes.Ldarg_0); // second parameter of type object[] from argument

il.Emit(OpCodes.Call, handler.GetMethodInfo());
il.Emit(OpCodes.Ret);