泛型如何被JIT编译器编译?

31

我知道泛型是由JIT(和其他所有东西一样)编译的,而模板则在编译代码时生成。
但问题在于,可以通过反射在运行时创建新的泛型类型。
这可能会影响已经通过语义解析器的泛型约束。

有人能解释一下这是如何处理的吗?发生了什么?
(包括代码生成和语义检查)


3
编译器不仅执行限制条件,JIT(Just-In-Time)编译器也会检查它们。泛型有几个比较复杂的方面,其中它们如何生成 ngen 是特别令人困惑的。这确实花了他们五年时间。 - Hans Passant
2个回答

39
жҲ‘жҺЁиҚҗйҳ…иҜ»гҖҠC#гҖҒJavaе’ҢC++дёӯзҡ„жіӣеһӢпјҡдёҺAnders Hejlsbergзҡ„еҜ№иҜқгҖӢгҖӮ
й—®1. жіӣеһӢеҰӮдҪ•иў«JITзј–иҜ‘еҷЁзј–иҜ‘пјҹ
д»ҺйҮҮи®ҝдёӯеҫ—зҹҘпјҡ
安德斯·海尔斯伯格:[...] 在CLR(公共语言运行时)中, 当编译List或任何其他泛型类型时, 它会将其编译成IL(中间语言)和元数据, 就像任何普通类型一样。IL和元数据包含了 额外的信息,知道有一个类型参数, 当然,从原则上讲,泛型类型的编译方式与 任何其他类型的编译方式相同。在运行时, 当应用程序首次引用List时,系统会查看 是否已经有人要求List<int>。 如果没有,它将IL和元数据传递给JIT, 以及List<T>的类型参数int。在JIT编译IL的过程中, 它还会替换类型参数。

[...]

接下来,我们为所有的类型实例化, 这些类型是值类型——例如List<int>List<long>List<double>List<float>——创建一个唯一的可执行本机代码副本。 所以List<int>有自己的代码。 List<long>有自己的代码。 List<float>有自己的代码。对于所有的 引用类型,我们共享代码, 因为它们在表示上是相同的。只是指针。


Qn 2. 问题是可以使用反射在运行时创建新的泛型类型。这当然会影响泛型的约束条件,而这些约束条件已经通过了语义解析器。有人能解释一下这是如何处理的吗?
Anders Hejlsberg: [...] 通过使用约束,你可以将动态检查从代码中移出,并在编译时或加载时进行验证。当你说K必须实现IComparable时,会发生几件事情。对于任何类型为K的值,你现在可以直接访问接口方法而无需进行强制转换,因为在程序语义上保证它将实现该接口。每当你尝试创建该类型的实例时,编译器将检查你提供给K参数的任何类型是否实现了IComparable,否则你将得到一个编译时错误。或者如果你使用反射来做,你将得到一个异常。
Bruce Eckel: 你说过编译器和运行时。
Anders Hejlsberg: 编译器进行检查,但你也可以使用反射在运行时进行检查,然后系统会进行验证。正如我之前所说,任何你可以在编译时做的事情,你也可以使用反射在运行时做到。

2
参考类型的泛型最终都会变成同一种类型;值类型的泛型会被单独实例化。
这是因为引用类型实际上只是对象引用(4或8个字节),而值类型是不同的,不能由单个代码处理,因为有栈布局差异等原因。因此,使用值类型实例化多个泛型类型将大大增加内存使用量,而使用引用类型实例化多个泛型类型则不会。

2
它们不是相同的类型。他们共享成员函数(方法)相同的机器码……但每个类型都有自己的静态成员变量的副本。 - Ben Voigt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接