【问题标题】:Dynamically call Func<T, object[], object> with Emit使用 Emit 动态调用 Func<T, object[], object>
【发布时间】:2017-03-05 19:12:35
【问题描述】:

我正在尝试动态创建函数,在执行时只调用给定的 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 或什么都没有。

我想要实现的是:

  • 对于给定的 Func,要覆盖一个方法,当它被调用时,这个 Func 会被触发并返回其结果,所有这些都使用 ILGenerator。我怎样才能做到这一点?我被困了好几天,没有任何效果。

【问题讨论】:

  • 你能添加一些关于你打算如何使用它的代码吗?我认为这会澄清你的意图。
  • 你可以试试il.Emit(OpCodes.Callvirt, handler.GetMethodInfo());,而不是:il.Emit(OpCodes.Callvirt, typeof(Func&lt;T, object[], object&gt;).GetMethod("Invoke"));
  • 嗯,将参数压入堆栈呢?您调用的方法需要参数。比如Tobject[] 之前的Callvirt

标签: c# system.reflection reflection.emit


【解决方案1】:

第一个问题是当你在 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);

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-05-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-03-05
    相关资源
    最近更新 更多