C# 如何实现虚拟泛型方法调用?
我对CLR如何实现这样的调用很感兴趣:C# 如何实现虚拟泛型方法调用?,c#,.net,clr,C#,.net,Clr,我对CLR如何实现这样的调用很感兴趣: abstract class A { public abstract void Foo<T, U, V>(); } A a = ... a.Foo<int, string, decimal>(); // <=== ? 抽象类A{ 公开摘要void Foo(); } A=。。。 a、 Foo();// 对。特定类型的代码由CLR在运行时生成,并保留实现的哈希表(或类似的) 第372页,共页: 当使用泛型类型的方法 参
abstract class A {
public abstract void Foo<T, U, V>();
}
A a = ...
a.Foo<int, string, decimal>(); // <=== ?
抽象类A{
公开摘要void Foo();
}
A=。。。
a、 Foo();// 对。特定类型的代码由CLR在运行时生成,并保留实现的哈希表(或类似的)
第372页,共页:
当使用泛型类型的方法
参数是JIT编译的,CLR
获取方法的IL,替换
指定类型参数,然后
创建特定的本机代码
该方法在计算机上运行
指定的数据类型。这正是
你想要的是什么,是主要的
泛型的特征。但是,
这样做的一个缺点是:CLR保持
为每个
方法/类型组合。这是
称为代码爆炸。这
最终可能会增加成本
应用程序的工作集
实质上,因此造成伤害
演出
幸运的是,CLR有一些
内置优化以减少
代码爆炸。首先,如果方法是
为特定类型参数调用,
然后,再次调用该方法
使用相同的类型参数,CLR
将为此编译代码
方法/类型组合只需一次。So
如果一个程序集使用列表,
和一个完全不同的组件
(加载在同一AppDomain中)也
使用列表时,CLR将
编译列表的方法
就一次。这减少了代码爆炸
实质上
我没有找到多少关于这一点的确切信息,所以大部分答案都是基于(甚至在.Net 1.0问世之前!)、中的一条简短注释以及我从中收集的信息(尽管我无法找到调用虚拟泛型方法的确切代码)
让我们从简单开始:如何调用非泛型非虚方法?通过直接调用方法代码,使编译后的代码包含直接地址。编译器从方法表中获取方法地址(见下一段)。能这么简单吗?嗯,差不多了。方法是JIT的这一事实使它变得有点复杂:实际调用的是编译方法的代码,如果它还没有编译,则只执行它;或者它是一条直接调用已编译代码的指令,如果它已经存在的话。我将进一步忽略这个细节
现在,如何调用非泛型虚拟方法?类似于C++语言中的多态性,有一个方法表可从<代码>这个指针(引用)访问。每个派生类都有自己的方法表及其方法。因此,要调用虚拟方法,请获取对this
(作为参数传入)的引用,然后获取对方法表的引用,查看其中的正确条目(对于特定函数,条目号是常量),并调用条目点所指向的代码。通过接口调用方法稍微复杂一些,但现在对我们来说并不有趣
现在我们需要了解代码共享。如果类型参数中的引用类型对应于任何其他引用类型,并且值类型完全相同,则代码可以在同一方法的两个“实例”之间共享。因此,例如C.M()
与C.M()
共享代码,而不是与C.M()
共享代码。类型参数和方法类型参数之间没有区别。(2001年的原始论文提到,当两个参数都是布局相同的struct
s时,代码也可以共享,但我不确定在实际实现中是否如此。)
让我们在通往泛型方法的道路上迈出中间的一步:泛型类型中的非泛型方法。由于代码共享,我们需要从某处获取类型参数(例如,用于调用代码,如newt[]
)。因此,泛型类型的每个实例化(例如C
和C
)都有自己的类型句柄,其中包含类型参数和方法表。普通方法可以从此
引用访问此类型句柄(技术上称为MethodTable
,尽管它包含的不仅仅是方法表)。有两种方法不能做到这一点:静态方法和值类型方法。对于这些类型,类型句柄作为隐藏参数传入
对于非虚拟泛型方法,类型句柄不够,因此它们会得到不同的隐藏参数,MethodDesc
,其中包含类型参数。此外,编译器不能将实例化存储在普通方法表中,因为这是静态的。因此,它为泛型方法创建了另一个不同的方法表,该表由类型参数索引,并从中获取方法地址(如果它已经存在并具有兼容的类型参数),或者创建一个新条目
虚拟泛型方法现在很简单:编译器不知道具体的类型,所以它必须在运行时使用方法表。并且普通方法表不能使用,因此它必须在特殊方法表中查找泛型方法。当然,包含类型参数的隐藏参数仍然存在
在研究这一点时,我们学到了一个有趣的小道消息:因为JITer非常懒惰,所以下面的代码(完全无用)可以工作:
object Lift<T>(int count) where T : new()
{
if (count == 0)
return new T();
return Lift<List<T>>(count - 1);
}
对象提升(int计数),其中T:new()
{
如果(计数=0)
返回新的T();
返回升降机(计数-1);
}
等效的C++代码导致编译器放弃堆栈溢出。
< P> <强>编辑< /强> < /P>
我现在遇到了,我现在遇到了,它清楚地表明,泛型在使用引用类型时正在重用相同的代码,因此我将接受它作为最终权威
原始答案
我在这里看到了两个不一致的答案,并且都提到了他们的立场,所以我将尝试添加我的两分钱
首先,微软Pres出版的Jeffrey Richter通过C#实现Clr