代码之家  ›  专栏  ›  技术社区  ›  Oskar Berggren

更改了.Net 4中泛型方法的LdToken行为?

  •  4
  • Oskar Berggren  · 技术社区  · 12 年前

    给定一个MethodInfo实例,该实例标识非泛型类的开放泛型方法,请考虑以下伪代码:

    class Foo { void FooMethod<T>() {} }
    
    public static void PrintMethodInfo(RuntimeMethodHandle methodHandle)
    {
        var mi = (MethodInfo) MethodBase.GetMethodFromHandle(methodHandle);
        Console.WriteLine("Method: "+mi.ToString());
    }
    
    var methodInfo = typeof(Foo).GetMethod("FooMethod");
    

    生成一个方法“void GeneratedMethod<T>()”,该方法在正文中包含以下代码:

    IL.Emit(OpCodes.Ldtoken, methodInfo);
    IL.Emit(OpCodes.Call, methodInfoPrintMethodInfo);
    

    调用GeneratedMethod<整数>(),.Net 3.5上的输出将为:

    Method: System.Object Method[Int32]()
    

    在.Net 4.0上,它将是:

    Method: System.Object Method[T]()
    

    因此,在.Net 2.0/3.5中,为ldtoken生成的IL将包含一个元数据令牌,该元数据令牌标识通用FooMethod<>使用GeneratedMethod<T>被调用。

    然而,在.Net 4.0中,ldtoken将包含标识开放泛型类型的元数据。

    我很难找到支持.Net 3.5情况下发生的事情的文档(事实上,如果生成的方法本身不是通用的,它应该完全失败)——.Net 4的行为似乎更合乎逻辑。我也找不到任何更改的文档。这是早期版本中的一个错误,现在已经修复了吗?

    1 回复  |  直到 12 年前
        1
  •  5
  •   Fabian Schmied    12 年前

    当您反汇编生成的代码时,您可以看到在.NET3.5中 ldtoken 按照如下方式发出与开放通用方法相关的指令:

    .method public static void  GeneratedMethod<T>() cil managed
    {
      // Code size       11 (0xb)
      .maxstack  1
      IL_0000:  ldtoken    method instance void [ConsoleApplication16]ConsoleApplication16.Program/Foo::FooMethod<!!0>()
      IL_0005:  call       void [ConsoleApplication16]ConsoleApplication16.Program::PrintMethodInfo(valuetype [mscorlib]System.RuntimeMethodHandle)
      IL_000a:  ret
    } // end of method TestType::GeneratedMethod
    

    语法 !!0 是对周围方法的类型参数的引用( GeneratedMethod ),所以 Foo 方法的实例加载 T 属于 GeneratedMethod<T> 。(事实上,这与为 IL.Emit (OpCodes.Ldtoken, methodInfo.MakeGenericMethod (<typeParameterOfGeneratedMethod>)) .)这个 !!0 即使在 生成的方法 根本不是通用的——生成的程序不再可验证(并且在执行时会导致BadImageFormatException)。

    这显然是一个错误,在.NET4中,这似乎已经修复,因为(已分解的)发出的代码现在看起来是这样的:

    .method public static void  GeneratedMethod<T>() cil managed
    {
      // Code size       11 (0xb)
      .maxstack  1
      IL_0000:  ldtoken    method instance void [ConsoleApplication16]ConsoleApplication16.Program/Foo::FooMethod<[1]>()
      IL_0005:  call       void [ConsoleApplication16]ConsoleApplication16.Program::PrintMethodInfo(valuetype [mscorlib]System.RuntimeMethodHandle)
      IL_000a:  ret
    } // end of method TestType::GeneratedMethod
    

    正如您所看到的,签名现在指的是未实例化的 FooMethod (在IL组件中,表示为 FooMethod[1] )。

    所以,是的,这看起来像是.NET 3.5中的一个错误,它是用.NET 4修复的。然而,这似乎并没有改变 ld令牌 ;只是Reflection.Emit没有正确地发出打开泛型方法的引用。我怀疑它也与 the fact that IL assembler didn't even have a syntax to denote open generic methods in the past