在为我设计的玩具语言编写一个编译器时,我看了一下有关用语言实现泛型的选项(通过搜索现有语言的示例),我开始想知道C#泛型。
我会尝试先描述一下我所理解的内容。如果我误解了任何事情,请随时纠正我。我将使用术语泛型类/类型/模板/定义来引用类似List<T>
和具体类/类型的内容来引用类似{{ 1}},List<int>
等
事先就帖子的长度道歉。
C ++ 使用模板进行泛型编程。据我了解,这意味着:
模板本身在二进制文件中不可用,因为它只是一个文本表示形式而不是具体类型。只有从中生成的具体类型才会可见。
(我我对这最后一部分不确定 - 如前所述,如果我关闭,请纠正我。)
Java 使用类型擦除,这意味着仅在编译时检查所有泛型类型参数的类型安全性,然后(如果未检测到类型不匹配),它们全部替换为对{的引用{1}}基类型,有效地为所有具体类型引用重用一个(非泛型)类。
现在,到实际问题。
在阅读an interview of Anders Hejlsberg(不是特别关联的,但他的观点相同)之后,他批评了Java的类型擦除,我认为 C#没有使用类型擦除。而且,既然我们可以反思C#中的具体类型,并且我们确实有LINQ这样的东西(它涉及C#的通用功能的相当复杂的用法),我们可以肯定地说C#确实不像Java一样使用类型擦除。
不知道任何其他选项,我认为C#使用类似模板。 不完全是C ++模板(至少我理解它们),因为我们显然可以创建一个具体类型,其泛型版本在另一个程序集中描述,因为我们可以再次反映类型并获取信息关于它。所以,我认为 C#泛型更像模板加上元数据 。
在某些时候,我在某处(虽然我无法找到链接)读到C#泛型类实际上是抽象类幕后。显然,这与我认为我所知道的不一致 - 很奇怪。
我忘记了几个月,但今天我偶然发现a question on SO very similar to this one(不幸的是,没有明确答案)。该问题的作者证明了C#编译器在编译时不会对泛型进行方法解析,即使在编译时可以知道的类型也是如此(使用List<string>
方法显示他创造了阴影Object
)。
好的,所以C#generics肯定不是&#34;文本替换加元数据&#34;,正如我原先想的那样。如果是,则在该问题中new
的(文本生成的)具体类型将导致编译器以非常不同的方式解析object.GetHashCode
调用。
但相反,C#编译器将其解析为类型只是Test
,用于泛型类型的所有具体实现,包括如果GetHashCode
object
是非通用代码,则会解析new
GetHashCode
。这使得它看起来像 C#泛型更接近类型擦除加上元数据 。现在我知道这个术语不太贴切:如果每个具体类的元数据都保留了类型参数信息,它就不是真正的类型擦除 - 但它确实类似于Java将所有内容存储为对象的方法(在至少对于参考类型,它们在低级别基本上是可互换的)并且来回传播。
我试图想象第三种可能性(正如我所说的那样,我无法找到任何来源 - 所以要带上一粒盐),泛型类表示为抽象类在幕后,每次生成一个新的具体类型时,编译器只是简单地扩展和智能地专门化,但我不能完全理解它在实践中如何工作。例如,泛型类的修饰符sealed
的语义必须被移位&#34;允许 类(在程序集中)扩展,但不允许扩展它的子级。一般来说,我认为它会使编译器(也可能是运行时)变得非常复杂,我甚至无法理解。
那么,如何在C#中真正实现泛型?虽然它们肯定存在差异,但是它们更接近于C ++还是更接近Java?或者是泛型定义真的表示为程序集中的特殊抽象类?或者它可能与我所描述的完全不同?
我不是在寻找一个特别详细的答案(尽管欢迎)。用简单的术语解释就好了,只要它清楚地突出了C#/ Java / C ++之间的差异,并且至少为我提供了关于C#编译器和运行时如何处理泛型类的理论知识。
编辑#1:我知道Eric Lippert强调了C ++模板和C#generics in at least one blog post之间的差异,基本上说&#34; C#generics不是模板&#34;但我不知道他们在幕后 的任何解释。
编辑#2:the linked question 的答案并未解决我提出的问题。该答案简要解释了一个特定的例子或现象,它是底层实现的结果,但绝对不能解释被问及的内容:底层实现是什么。
答案 0 :(得分:2)
请参阅此处:内部工作方式:http://microsoftdev.blogspot.in/2007/07/how-generics-work.html
并且C ++模板和C#泛型之间的区别在于: