我对CLR如何实现这样的调用感兴趣:
abstract class A {
public abstract void Foo<T, U, V>();
}
A a = ...
a.Foo<int, string, decimal>(); // <=== ?
Run Code Online (Sandbox Code Playgroud)
这个调用是否会导致某种类型的哈希映射查找类型参数令牌作为键和编译的泛型方法特化(一个用于所有引用类型,所有值类型的不同代码)作为值?
svi*_*ick 13
我没有找到关于这方面的确切信息,所以这个答案的大部分是基于2001年的.Net泛型的优秀论文(甚至在.Net 1.0出来之前!),后续文章中的一个简短说明以及什么我收集了SSCLI v.2.0源代码(尽管我无法找到调用虚拟泛型方法的确切代码).
让我们开始简单:如何调用非泛型非虚方法?通过直接调用方法代码,使编译后的代码包含直接地址.编译器从方法表中获取方法地址(参见下一段).可以这么简单吗?好吧,差不多.方法是JITed这一事实使它变得有点复杂:实际调用的是编译方法的代码,然后只执行它,如果它还没有编译; 或者它是一个直接调用已编译代码的指令(如果它已经存在).我将进一步忽略这一细节.
现在,如何调用非泛型虚拟方法?与C++等语言中的多态性类似,可以从this指针(引用)访问方法表.每个派生类都有自己的方法表及其方法.因此,要调用虚方法,获取引用this(作为参数传入),从那里获取对方法表的引用,查看其中的正确条目(条目号对于特定函数是常量)并调用条目指向的代码.通过接口调用方法稍微复杂一点,但现在对我们来说并不感兴趣.
现在我们需要了解代码共享.如果类型参数中的引用类型对应于任何其他引用类型,则代码可以在同一方法的两个"实例"之间共享,并且值类型完全相同.因此,例如,C<string>.M<int>()共享代码C<object>.M<int>(),但不与C<string>.M<byte>().类型类型参数和方法类型参数之间没有区别.(2001年的原始文章提到,当两个参数都struct具有相同的布局时,代码也可以共享,但我不确定在实际实现中是否正确.)
让我们在通用方法的过程中做一个中间步骤:泛型类型中的非泛型方法.由于代码共享,我们需要从某个地方获取类型参数(例如,用于调用代码new T[]).出于这个原因,泛型类型(例如C<string>和C<object>)的每个实例都有自己的类型句柄,它包含类型参数和方法表.普通方法可以MethodTable从this引用访问此类型句柄(技术上是一个容易混淆的结构,即使它包含的不仅仅是方法表).有两种类型的方法无法做到这一点:静态方法和值类型方法.对于那些,类型句柄作为隐藏参数传入.
对于非虚拟泛型方法,类型句柄是不够的,因此它们获得MethodDesc包含类型参数的不同隐藏参数.此外,编译器无法将实例存储在普通方法表中,因为它是静态的.因此,它为泛型方法创建了第二个不同的方法表,该表由类型参数索引,并从那里获取方法地址(如果已存在兼容类型参数),或创建新条目.
虚拟泛型方法现在很简单:编译器不知道具体类型,因此它必须在运行时使用方法表.并且不能使用普通方法表,因此必须在特殊方法表中查找泛型方法.当然,包含类型参数的隐藏参数仍然存在.
在研究这个问题时学到了一个有趣的知识:因为JITer非常懒惰,以下(完全无用的)代码可以工作:
object Lift<T>(int count) where T : new()
{
if (count == 0)
return new T();
return Lift<List<T>>(count - 1);
}
Run Code Online (Sandbox Code Playgroud)
等效的C++代码会导致编译器放弃堆栈溢出.
| 归档时间: |
|
| 查看次数: |
2804 次 |
| 最近记录: |