【问题标题】:Changed behaviour of LdToken for generic methods in .Net 4?.Net 4 中泛型方法的 LdToken 行为改变了吗?
【发布时间】:2012-09-02 00:43:22
【问题描述】:

给定一个识别非泛型类的开放泛型方法的 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()”:

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,该通用 FooMethod 使用调用 GeneratedMethod 时给出的类型参数进行实例化。

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

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

【问题讨论】:

  • “调用 GeneratedMethod 时给出的类型参数”是什么意思?你是说你生成的 GeneratedMethod 是通用的,并且你用 int 作为类型参数来调用它?
  • @svick 是的,没错。我进行了编辑,以便格式化程序不会吞下通用参数。

标签: .net reflection .net-4.0 reflection.emit


【解决方案1】:

反汇编生成的代码可以看到,在.NET 3.5中,与open泛型方法相关的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方法是用属于GeneratedMethod&lt;T&gt;T实例化加载的。 (事实上​​,这与为IL.Emit (OpCodes.Ldtoken, methodInfo.MakeGenericMethod (&lt;typeParameterOfGeneratedMethod&gt;)) 发出的IL 相同。)即使GeneratedMethod 根本不是通用的,也会发出这个!!0 引用——然后生成的程序不再可验证(并且,当执行时, 导致 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

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

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

【讨论】:

  • 感谢您确认我不是唯一在这里看到问题的人。幸运的是,在我的例子中,应用程序代码在使用 Reflection.Emit() 时实际上是在作弊,并且使其更加严格使得代码在 3.5 和 4.0 上的工作方式相同。
猜你喜欢
  • 1970-01-01
  • 2012-03-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-11-05
  • 1970-01-01
  • 2011-12-27
  • 1970-01-01
相关资源
最近更新 更多