泛型类的定义(generic class definition)属于完全编译的MSIL类型,其代码对于任何一种可供使用的类型参数来说都必须完全有效。这样的定义叫做泛型类型定义(generic class definition)。对于泛型类型来说,如果所有的类型参数都已经指明,那么这种泛型类型就称为封闭式泛型类型(closed generic type),反之,若仅仅指出了某些参数,则称为开放式泛型类型(open generic type)。
与真正的类型相比,MSIL形式的泛型只是定义好了其中的某一部分而已,必须把里面的占位符替换成具体的内容才能令其成为完备的泛型类型(completed generic type),这项工作是由JIT编译期在创建机器码的时候完成的,这些机器码会在程序运行期根据早前的泛型定义实例化出封闭式的泛型类型。这样会产生很多种封闭的泛型类型,从而增加了袋码开销,但其好处则是降低了执行程序所花的时间以及存储数据所需要的空间。这个过程会运用在开发者所编写的每一个类型上面,泛型类与非泛型类都是 如此。对于非泛型类型来说,IL(MSIL)形式的类与JIT所创建的机器码之间是一一对应的关系,只要有这样一个类,JIT就会为其创建出一份想要的机器码;对于泛型类来说,JIT编译期则会判断类型参数,并据此生成特定的指令。他可以通过各种技术把不同的类型参数合并成同一份机器码。
如下面三种写法在程序运行的时候执行的是同一份机器码:
List<string> stringList = new List<string>();
List<Stream> OpenFiles = new List<Stream>();
List<MyClassType> anotherList = new List<MyClassType>();
以下三种写法所对应的的封闭式泛型类型的机器码是不一样的
List<double> doubleList = new List<double>();
List<int> markers = new List<int>();
List<MyStruct> anotherList = new List<MyStruct>();
他们之间的区别对于编程的意义是体现在内存占用量上。
如果泛型定义(可能是泛型方法或泛型类的定义)中至少有一个类型参数是值类型,那么当运行期系统对该定义做JIT编译(JIT-compile)时,他就要分两步来实行。首先新建IL形式的类,用以表示封闭式的泛型类型。这是一种稍加简化的说法,也就是说,运行期系统会把泛型定义中所提到的类型参数T全部换成int这样的值类型。替换完毕后,开始第二步,也就是对必要的代码做JIT编译,将其转化为X86指令。由于JIT编译期并不会在类刚刚加载进来时就创建出整个类的X86码,因此必须像这样分两步。
这样看来JIT编译器只需先把IL中提到的类型参数全部替换成值类型即可,等真正用到的时候再对替换好的IL码做JIT编译。这种做法使得程序在运行的时候会占用更多的内存,因为每用到一种值类型,就需要对IL形式的定义做一次替换,以便生成对应的封闭式泛型类型,而这种类型中的每一个方法也都必须单独用一段机器码来表示。
但这样也是有好处的,因为可以避开值类型的装箱与解除装箱等操作,使得与之相关的代码及数据能够变得少一些。此外由于类型安全问题忽悠编译器确保,因此运行的时候不用做那么多检查,这可以缩减程序尺寸,并提升性能。