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

Osk*_*ren 4 .net reflection reflection.emit .net-4.0

给定一个标识非泛型类的开放泛型方法的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");
Run Code Online (Sandbox Code Playgroud)

生成一个方法"void GeneratedMethod <T>()",在主体中包含此代码:

IL.Emit(OpCodes.Ldtoken, methodInfo);
IL.Emit(OpCodes.Call, methodInfoPrintMethodInfo);
Run Code Online (Sandbox Code Playgroud)

调用GeneratedMethod <int>(),.Net 3.5上的输出将是:

Method: System.Object Method[Int32]()
Run Code Online (Sandbox Code Playgroud)

在.Net 4.0上,它将是:

Method: System.Object Method[T]()
Run Code Online (Sandbox Code Playgroud)

因此,在.Net 2.0/3.5中,ldtoken生成的IL似乎将包含一个元数据标记,该标识符标识通用FooMethod <>实例化,该类型参数在调用GeneratedMethod <T>时给出.

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

我很难找到支持.Net 3.5案例中发生的事情的文档(事实上,如果生成的方法本身不是通用的,它应该完全失败) - .Net 4的行为似乎更合乎逻辑.我也找不到任何关于这种变化的文件.这是现在修复的早期版本中的错误吗?

Fab*_*ied 5

当您反汇编生成的代码时,您可以看到在.NET 3.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
Run Code Online (Sandbox Code Playgroud)

语法!!0是对周围方法(GeneratedMethod)的类型参数的引用,因此该Foo方法是使用T所属的实例化加载的GeneratedMethod<T>.(实际上,这与发出的IL相同IL.Emit (OpCodes.Ldtoken, methodInfo.MakeGenericMethod (<typeParameterOfGeneratedMethod>)).)!!0即使GeneratedMethod不是通用的,也会发出此引用- 结果程序不再可验证(并且在执行时会导致BadImageFormatException).

这显然是一个错误,在.NET 4中,这似乎是固定的,因为(反汇编)发出的代码现在看起来像这样:

.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
Run Code Online (Sandbox Code Playgroud)

如您所见,签名现在指的是未实例化的FooMethod(在IL程序集中,这表示为FooMethod[1]).

所以是的,这看起来像.NET 3.5中的一个错误,它是用.NET 4修复的.但是,它似乎并没有改变语义ldtoken; 只是Reflection.Emit没有正确地发出对开放泛型方法的引用.我怀疑它也与IL汇编程序在过去甚至没有表示开放泛型方法的语法有关.