CLR via C# 泛型 泛型基础结构

泛型在CLR2.0中加入。为了在CLR中加入泛型,许多人花费了大量时间来完成这个大型任务。具体地说,为了使泛型能够工作,Microsoft必须完成以下工作。

1.创建新的IL指令,使之能够识别类型实参。

2.修改现有元数据表的格式,以便表示具有泛型参数的类型名称和方法。

3.修改各种编程语言(C#等)来支持新语法,允许开发人员定义和引用泛型类型和方法。

4.修改编译器,使之能生成新的IL指令和修改的元数据格式。

5.修改JIT编译器,以便处理新的支持类型实参的IL指令来生成正确的本机代码。

6.创建新的反射成员,使开发人员能查询类型和成员,以判断它们是否具有泛型参数。另外,还必须定义新的反射成员,使开发人员能在运行时创建泛型类型和方法定义。

7.修改调试器以显示和操纵泛型类型、成员、字段以及局部变量。

8.修改VS的“智能感知”功能。将泛型类型或方法应用于特定数据类型时能显示成员的原型。

//--开放类型和封闭类型

具有泛型类型参数的类型仍然是类型,CLR同样会为它创建内部的类型对象。这一点适合应用类型(类)、值类型(结构)、接口类型和委托类型。然而,具有泛型类型参数的类型称为开放类型,CLR禁止构造开放类型的任何实例。这类似于CLR禁止构造接口类型的实例。

代码引用泛型类型时可指定一组泛型类型实参。为所有类型参数都传递了实际的数据类型类型就称为封闭类型。CLR允许构造封闭类型的实例。然而,代码引用泛型类型的时候,可能留下一些泛型类型实参未指定。这会在CLR中创建新的开放类型对象,而且不能创建该类型的实例。

//--泛型类型和继承

泛型类型仍然是类型,所以能从其他任何类型派生。使用泛型类型并指定类型实参时,实际是在CLR中定义一个新的类型对象,新的类型对象从泛型类型派生自的那个类型派生。换言之,由于List<T>从object派生,所以List<string>和List<Guid>也从object派生。指定类型实参不影响继承层次结构。

假定像下面这样定义一个链表节点类:

CLR via C# 泛型 泛型基础结构

那么可以写代码来构造链表:

CLR via C# 泛型 泛型基础结构

在这个Node类中,对于m_next字段引用的另一个节点来说,它的m_data字段必须包含相同的数据类型,这意味着在链表包含的节点中,所有数据项都必须具有相同的类型(或派生类型)。例如,不能使用Node类来创建这样一个链表:其中一个元素包含Char值,另一个包含DateTime值,另一个元素包含string值。当然,如果到处使用Node<Object>那么确实可以做到,但会丧失编译时类型安全性,而且值类型会被装箱。

所以,更好的办法是定义非泛型Node基类,再定义泛型TypedNode类(以Node作为基类)。这样就可以创建一个链表,其中每个节点都可以是一种具体的数据类型(不能是object),同时获得编译时的类型安全性,并防止值类型装箱。

CLR via C# 泛型 泛型基础结构

现在可以写代码来创建一个链表,其中每个节点都可以是不同数据类型

CLR via C# 泛型 泛型基础结构

//--泛型类型同一性

泛型语法有时会将开发人员弄糊涂,因为源代码中可能散步着大量“<”和“>”符号,这有损可读性。为了对语法进行增强,有的开发人员定义了一个新的非泛型类类型,它从一个泛型类型派生,并指定了所有的类型实参。

例如,为了简化下面这样的代码:

CLR via C# 泛型 泛型基础结构

一些开发人员可能首先定义下面这样的类:

CLR via C# 泛型 泛型基础结构

然后就可以简化创建列表的代码(没有“<”和“>”符号):

CLR via C# 泛型 泛型基础结构

这样做表面上是方便了(尤其是要为参数、局部变量和字段使用新类型的时候),但是,绝对不要单纯出于增强源码可读性的目的来定义一个新类。这样会丧失类型同一性(identity)和相等性(equivalence)。

CLR via C# 泛型 泛型基础结构

上述代码运行时,sameType会被初始化为false,因为比较的是两个不同类型的对象。这也意味着如果方法的原型接受一个DateTimeList,那么不可以将一个List<DateTime>传给它。然而,如果方法的原型接受一个List<DateTime>,那么可以将一个DateTimeList传给它,因为DateTimeList从List<DateTime>派生。开发人员很容易被所有这一切搞糊涂。

幸好,C#允许使用简化的语法来引用泛型封闭类型,同时不会影响类型的相等性。这个语法要求在源文件顶部使用传统using指令:

CLR via C# 泛型 泛型基础结构

using指令实际定义的是名为DateTimeList的符号。代码编译时,编译器将代码中出现的所有DateTimeList替换成System.Collections.Generic.List<System.DateTime>。这样就允许开发人员使用简化的语法,同时不影响代码的实际含义。所以,类型的同一性和相等性得到了维持。

CLR via C# 泛型 泛型基础结构

现在执行上述代码时,sameType会被初始化为ture。

另外也可以使用C#的“隐式类型局部变量”功能,让编译器根据表达式的类型来推断方法的局部变量的类型:

CLR via C# 泛型 泛型基础结构