在 运行 时间内创建的方法会根据它的状态更改另一个方法调用的参数顺序 运行

Method created during runtime changes the parameter order of another method call depending on how it is run

我在 运行 期间使用 Reflection.Emit 实现接口并创建它们定义的方法。

接口中方法的示例定义:

IFoo DoSomething(IBar bar, string name);

要创建方法,我执行以下操作:

var args = methodInfo.GetParameters();

MethodBuilder methodBuilder = typeBuilder.DefineMethod(methodInfo.Name, MethodAttributes.Public | MethodAttributes.Virtual,
    methodInfo.ReturnType, (from arg in args select arg.ParameterType).ToArray());
typeBuilder.DefineMethodOverride(methodBuilder, methodInfo);

var generator = methodBuilder.GetILGenerator();

generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldfld, fieldBuilder);
generator.Emit(OpCodes.Ldtoken, methodInfo.ReturnType);

if (args.Any())
{
    generator.Emit(OpCodes.Ldc_I4_S, args.Length);
    generator.Emit(OpCodes.Newarr, typeof(object));

    for (int i = 0; i < args.Length; i++)
    {
        generator.Emit(OpCodes.Dup);
        generator.Emit(OpCodes.Ldc_I4_S, i);
        generator.Emit(OpCodes.Ldarg_S, i + 1);
        generator.Emit(OpCodes.Stelem_Ref);
    }
}
else
{
    generator.Emit(OpCodes.Ldc_I4_0);
}

generator.EmitCall(OpCodes.Callvirt, typeof(ISomeType).GetMethod(nameof(ISomeType.Test), new[] {typeof(Type), typeof(object[])}), null);
generator.Emit(OpCodes.Ret);

例如,这会生成我的 DoSomething(IBar bar, string name) 接口方法。
所有生成的方法都调用ISomeType.

的方法Test()

这是方法Test():

public object Test(Type type, object[] arguments)
{
   //do something
}

当我 运行 我的应用程序并调用 DoSomething() 方法时,参数以错误的方式(第一个 object[]Type 第二个)传递给 Test(),这显然会导致异常。

但是当我 运行 进行单元测试并调用 DoSomething() 方法时,参数被正确传递(首先是 Type,然后是 object[])到 Test().

为什么参数传递给 Test() 方法的顺序会有所不同,具体取决于 运行?

因此,在评论中 Marc Gravell 的大量帮助下,我找到了解决此问题的方法。
首先,我在创建方法时遇到了错误。

方法没有参数的情况必须从

改变
generator.Emit(OpCodes.Ldc_I4_0);

0 作为 Int32 压入堆栈,

MethodInfo emptyArray = typeof(Array).GetMethod(nameof(Array.Empty))?.MakeGenericMethod(typeof(object));
generator.EmitCall(OpCodes.Call, emptyArray, null);

这实际上是将一个空数组压入堆栈,并将其作为参数传递给 Test() 函数。

要解决参数顺序错误的问题,在我的具体情况下有两种可能的解决方案:

(1)对生成的IL代码的更多改动:

  1. 真正将方法的 return 类型推送为 Type 而不是 RuntimeTypeHandle:

    generator.Emit(OpCodes.Ldtoken, methodInfo.ReturnType);
    
    MethodInfo getTypeFromHandle = typeof(Type).GetMethod(nameof(Type.GetTypeFromHandle));
    generator.EmitCall(OpCodes.Call, getTypeFromHandle, null);
    
  2. 在 returning:

    之前将 Test() 的 return 类型转换为所需的 return 类型的方法
    generator.EmitCall(OpCodes.Callvirt, typeof(ISomeType).GetMethod(nameof(ISomeType.Test), new[] {typeof(Type), typeof(object[])}), null);
    generator.Emit(OpCodes.Castclass, methodInfo.ReturnType);
    generator.Emit(OpCodes.Ret);
    

(2)将Test()方法转换为泛型方法:

如果您有参数作为 Type,您可以将您的方法转换为通用方法:

public T Test<T>(object[] arguments)
{
   //do something
}

如果您决定这样做,您还必须更改一些 IL 代码生成:

  1. 不要将方法的 return 类型压入堆栈:

    删除这一行:

    generator.Emit(OpCodes.Ldtoken, methodInfo.ReturnType);
    
  2. 更改Test()的调用:

    MethodInfo test = typeof(ISomeType).GetMethod(nameof(ISomeType.Test), new[] { typeof(object[]) }).MakeGenericMethod(methodInfo.ReturnType);
    generator.EmitCall(OpCodes.Callvirt, test, null);
    generator.Emit(OpCodes.Ret);
    

    在这种情况下,您也不必强制转换 return 值,因为您的方法 Test() 已经 return 具有 T 的正确类型。

对于这两种解决方案中的任何一种,Test() 方法都会以正确的参数顺序进行调用,而不管它是如何调用的(应用程序或单元测试)。

另一个 of Marc Gravell was to use Sigil 在您生成的 IL 代码无效时获得清晰的错误消息。而且无论你选择上面两种解决方案中的哪一种,我都只能推荐这样做。