More Effective C# :使用泛型

    自从.NET 2.0添加的泛型,从很大程度上影响了开发人员编写代码的方法和方式。泛型不仅仅只应用在集合上,它在涉及接口、抽取算法等方面,也带来了巨大的影响。

    泛型类定义能够完整的编译为MSIL类型,对于任何满足约束的类型参数,泛型类型中包含的代码必须保证完全合法。所有类型参数都已经明确给出的泛型类型被称作封闭泛型类型,仅给出了一部分类型参数的泛型类型被称作开放泛型类型。

    单纯的讲使用泛型会减小程序的大小,是不正确的,影响程序大小的因素包括程序中使用的类型参数的个数以及创建出的封闭泛型类型的个数。

    IL中的泛型可以看做是某个实际类型定义的一部分。IL为初始化某个完整的泛型类型实例预留了占位符。JIT编译器将在运行时生成机器代码时补全该封闭泛型类型的定义。这样的处理方法带来了一个矛盾:其劣势在于多个封闭泛型类型会增大处理代码的开销,而优势在于存储数据的时间/空间开销会减少。

    不同的封闭泛型类型可能会导致代码生成不同的最终运行时形式。在创建多个封闭泛型类型时,JIT编译器和CLR都会进行优化,以降低对存的压力。IL形式的程序集被加载到内存中的数据页,只有在JIT编译器将IL代码转换成机器指令后,生成的机器码才会被放置于只读的代码页中。

    无论是否泛型,每个类型都会执行上述过程,对于非泛型类型,类的IL代码和它生成的机器码之间是一对一的关系,而泛型的出现则让转换的过程变得略加复杂,在JIT对泛型类进行转换时,JIT编译器将检查当前的类型参数,并根据该信息生成特定的指令。JIT编译器将会对该过程进行一系列的优化,以便让不同类型参数能够使用同样的机器码。

    对于引用类型生成的泛型类来说,JIT编译器会生成唯一的机器码版本,来看下面的代码。

1 private static void InitRefTypeGen()
2 {
3 List<string> stringList = new List<string>();
4 List<Stream> openFiles = new List<Stream>();
5 List<Employee> empList = new List<Employee>();
6 }
    上述代码在运行时的机器码是完全相同的。

    对于值类型生成的泛型类来说,JIT编译器会为不同类型参数创建不同版本的机器码,来看下面的代码。

1 private static void InitValTypeGen()
2 {
3 List<double> doubleList = new List<double>();
4 List<int> intList = new List<int>();
5 List<Name> nameList = new List<Name>();
6 }
    上述代码在运行时的机器码是不一样的。

    当运行时需要JIT编译一个泛型定义时,且至少有一个类型的参数是值类型时,那么这个过程可以分成两个步骤:1. 编译器将创建一个新的IL类,用来表示该封闭泛型类型。2. JIT将该代码编译成X86指令。这两个步骤非常有必要,因为JIT并不是在某个类加载时就为其生成完整的X86指令的,而是仅在类中的每个方法被第一次调用时才开始编译的。这样,框架有必要在IL代码上先执行一个替换的步骤,随后再像普通类定义一样按需编译。

    这也意味着运行时的额外内存占用将分为如下两部分:1. 为每种用值类型作为参数的封闭泛型类型保存一份IL定义的副本。2. 为每种用值类型作为参数的封闭类型保存一份所调用方法的机器码的副本。

posted @ 2010-03-14 10:25  李潘  阅读(650)  评论(2编辑  收藏  举报