泛型如何实现?


16

从编译器内部角度来看,这是一个问题。

我对泛型感兴趣,而不是模板(C ++),所以我用C#标记了问题。不是Java,因为AFAIK两种语言的泛型在实现上有所不同。

当我查看不带泛型的语言时,它非常简单,您可以验证类定义,将其添加到层次结构中就可以了。

但是如何处理泛型类,更重要的是如何处理对它的引用?如何确保每个实例的静态字段都是奇异的(即每次解析通用参数时)。

假设我看到一个电话:

var x = new Foo<Bar>();

是否将新Foo_Bar类添加到层次结构?


更新:到目前为止,我仅找到2条相关的帖子,但是即使它们在“如何自己做”的意义上也没有涉及太多细节:


投票是因为我认为完整的答案会很有趣。我对它的工作原理有一些想法,但不足以准确回答。我不认为C#中的泛型会针对每种泛型类型编译为专用类。它们似乎在运行时已解决(使用泛型可能会显着提高速度)。也许我们可以让埃里克·利珀特(Eric Lippert)受到鼓舞?
KChaloux

2
@KChaloux:在MSIL级别上,有一个关于泛型的描述。当JIT运行时,它将为用作通用参数的每种值类型创建单独的机器代码,并再提供一组覆盖所有引用类型的机器代码。在MSIL中保留通用描述确实很棒,因为它允许您在运行时创建新实例。
Ben Voigt

@Ben这就是为什么我没有尝试真正回答问题的原因:p
KChaloux

我不确定您是否还在,但是您要编译哪种语言。这将对实现泛型的方式产生很大影响。我可以在前端提供有关我通常如何使用它的信息,但是后端可能会有很大差异。
Telastyn

@Telastyn,对于那些主题,我肯定是:-)我正在寻找真正接近C#的东西,就我而言,我正在编译 PHP(不开玩笑)。如果您分享您的知识,我将不胜感激。
greenoldman

Answers:


4

如何确保每个实例的静态字段都是奇异的(即每次解析通用参数时)。

每个通用实例都有其自己的(易名)MethodTable副本,该副本存储在静态字段中。

假设我看到一个电话:

var x = new Foo<Bar>();

是否将新Foo_Bar类添加到层次结构?

我不确定将类层次结构视为运行时实际存在的某种结构是否有用,它更像是一种逻辑构造。

但是,如果考虑使用MethodTables(每个方法表都有一个指向其基类的间接指针)来形成此层次结构,那么是的,这会将新类添加到层次结构中。


谢谢,这很有趣。因此,静态字段的解决方法类似于虚拟表,对吗?是否有对“全局”词典的引用,该词典保存每种类型的条目?因此,我可能有2个程序集彼此不认识,Foo<string>并且不会从中产生两个静态字段实例Foo
greenoldman

1
@greenoldman好吧,与虚拟表不完全相同。MethodTable同时包含静态字段和对在虚拟分派中使用的类型的方法的引用(这就是为什么将其称为MethodTable)的原因。是的,CLR必须具有一些可用于访问所有MethodTables的表。
svick

2

我在那里看到两个实际的具体问题。您可能想问其他相关问题(作为单独的问题,带有指向此问题的链接)以得到全面的了解。

如何为每个通用实例给静态字段单独的实例?

好吧,对于与泛型类型参数无关的静态成员,这非常简单(使用从泛型参数映射到值的字典)。

与类型参数相关的成员(静态的或非静态的)可以通过类型擦除来处理。只需使用最严格的约束(通常是System.Object)即可。由于在编译器类型检查之后会擦除类型信息,因此这意味着将不需要运行时类型检查(尽管在运行时可能仍然存在接口强制类型转换)。

每个泛型实例是否在类型层次结构中分别出现?

不在.NET泛型中。决定从类型参数中排除继承,因此事实证明,泛型的所有实例都在类型层次结构中占据相同的位置。

这可能是一个不错的决定,因为如果无法从基类中查找名称,将令人惊讶。


我的问题是我无法摆脱模板方面的思考。例如-与模板通用类不同完全编译的。这意味着在其他使用此类的程序集中会发生什么?用内部转换调用已经编译的方法?我怀疑仿制药可以依靠约束-而上的说法,否则Foo<int>Foo<string>会与打相同的数据FooW / O限制。
greenoldman

1
@greenoldman:我们可以暂时避免使用值类型,因为它们实际上是经过特殊处理的吗?如果您有List<string>and List<Form>,则由于List<T>内部具有type成员,T[]并且没有任何约束T,那么您实际得到的是操纵的机器代码object[]。但是,由于仅将T实例放入数组,因此所有返回的结果都可以作为,T而无需进行其他类型检查。另一方面,如果有ControlCollection<T> where T : Control,则内部数组T[]将变为Control[]
Ben Voigt

我是否正确理解约束已被采用并用作内部类型名,但是在实际使用类时使用了强制类型转换?好的,我了解该模型,但是给人的印象是Java使用了它,而不是C#。
greenoldman

3
@greenoldman:Java在source-> bytecode转换步骤中执行类型擦除。这使得验证者无法验证通用代码。C#在字节码->机器代码步骤中执行此操作。
Ben Voigt

@BenVoigt Java中保留了有关泛型类型的某些信息,否则,如果没有它的源代码,您将无法针对使用泛型的类进行编译。它只是不保留在字节码序列本身的AIUI中,而是保留在类元数据中。
Donal Fellows 2013年

1

但是如何处理泛型类,更重要的是如何处理对它的引用?

编译器前端的一般方法是具有两种类型实例,即泛型类型(List<T>)和绑定泛型类型(List<Foo>)。泛型类型定义了存在的功能,哪些字段以及在何处T使用泛型类型引用。绑定的泛型类型包含对泛型类型的引用以及一组类型参数。那里有足够的信息供您生成具体类型,然后用Foo或任何类型参数替换通用类型引用。当您进行类型推断并且需要推断List<T>vs 时,这种区别非常重要List<Foo>

与其考虑泛型之类的模板(直接构建各种实现),不如将它们视为功能语言类型的构造函数(泛型参数就像是赋予您类型的函数中的参数),可能会有所帮助。

至于后端,我真的不知道。我所有与泛型有关的工作都以CIL为后端,因此我可以将它们编译为受支持的泛型。


非常感谢您(可惜我不能接受多重答案)。很高兴听到我正确地完成了这一步-在我的情况下,它List<T>拥有实类型(它的定义),而List<Foo>(我也感谢术语部分)我的方法保持了List<T>(当然现在绑定到Foo而不是T)。
greenoldman 2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.