
5.5 泛型
泛型是一种特殊的类,其最主要的应用就是创建集合类。C#中的数据类型有多种,用来处理不同的数据。而泛型替代了C#中的数据类型,泛型在定义时并不使用数据类型,而在使用时另外定义其数据类型。
5.5.1 泛型概述
计算机处理数据,需要依据数据类型来执行,方便计算机内部的运转。而泛型使用符号T作为数据类型,替代所需要使用的数据类型。在调用前,需要为T定义一个数据类型,以供程序执行。
在C#中,类和方法均可以定义为泛型类型,使用尖括号和包含在内的符号<T>。如定义一个泛型类,名称为List,使用如下语句:
public class List<T>{}
使用泛型可以减少数据类型的转化,尤其是在需要装箱和拆箱的时候。装箱和拆箱很容易操作,但多余的操作使得系统性能损失,其特点如下所示。
(1)使用泛型集合类可以提供更高的类型安全性。
(2)使用泛型类型可以最大限度地重用代码、保护类型的安全以及提高性能。
(3)泛型最常见的用途是创建集合类。
(4)可以对泛型类进行约束以访问特定数据类型的方法。
(5)关于泛型数据类型中使用的类型的信息可在运行时通过反射获取。
泛型类和泛型方法同时具备可重用性、类型安全和效率,泛型通常用在集合和在集合上运行的方法中。.NET Framework类库提供了命名空间System.Collections.Generic,包含几个新的基于泛型的集合类,如List类。
泛型同样支持用户自定义,创建自定义的泛型类和方法,设计类型安全的高效模式以满足需求。
技巧
大多数情况下,直接使用.NET Framework类库提供的List<T>类即可,不需要自行创建类。
在通常使用具体类型来指示列表中所存储项的类型时,使用类型参数T。它的使用方法如下。
(1)在AddHead()方法中作为方法参数的类型。
(2)在Node嵌套类中作为公共方法GetNext()和Data属性的返回类型。
(3)在嵌套类中作为私有成员数据的类型。
注意
T可用于Node嵌套类,但使用具体类型实例化GenericList<T>,则所有的T都将被替换为具体类型。
5.5.2 泛型类
泛型类常用于自定义集合,如List集合中元素的添加、移除等操作的执行与元素的数据类型无关,泛型类针对的就是不特定于具体数据类型的操作。
在.NET Framework类库中提供了泛型集合类,可以直接使用。这些泛型类大多在System.Collections.Generic命名空间下。其常用泛型集合类及其说明如表5-18所示。
表5-18 泛型类

前几节讲述的集合类ArrayList集合、Hashtable集合、Queue集合、Stack集合和SortedList集合,可以使用对应的泛型类替换。将集合类对应到泛型类,对应效果如下。
(1)ArrayList对应List。
(2)Hashtable对应Dictionary。
(3)Queue对应Queue。
(4)Stack对应Stack。
(5)SortedList对应SortedList。
泛型类的创建可以从一个现有的具体类开始,逐一将每个类型改为类型参数,但要求更改后的类成员既要通用化,又要实际可用。自定义泛型类时,需要注意以下几点。
(1)能够参数化的类型越多,代码就会变得越灵活,重用性就越好。但是,太多的通用化会使其他开发人员难以阅读或理解代码。
(2)应用尽可能多的约束,但仍能够处理需要处理的类型。例如,如果泛型类仅用于引用类型,则应用类约束。这可以防止类被意外地用于值类型,并允许对T使用as运算符以及检查空值。
(3)由于泛型类可以作为基类使用,此处适用的设计注意事项与集合类相同。
(4)判断是否需要实现一个或多个泛型接口。
如系统内置的泛型类List类,List类内部使用字符T替换了数据类型名称。但如果对List类的类型T使用引用类型,则两个类的行为是完全相同的;如果对类型T使用值类型,则需要考虑实现和装箱问题。
类List继承了多个泛型接口和非泛型接口,其声明语句如下:
public class List<T>:IList<T>, ICollection<T>, IEnumerable<T>, IList, ICollection, IEnumerable
5.5.3 泛型方法和参数
泛型方法是使用泛型类型参数声明的方法,而方法中参数的类型需要在调用时指定。同泛型类的声明一样,泛型方法在声明或定义时添加<T>,并在泛型参数前使用符号T。
并不是只有在泛型类中才能创建泛型方法,泛型方法可用于泛型类和非泛型类。泛型方法的类型及泛型参数的类型可以省略,编译器将根据传入的参数确定方法及参数的类型。
警告
若泛型方法没有参数,在调用时不能省略泛型方法的类型。
泛型方法也支持重载,当参数数据类型不同时,需要使用不同的字符表示。如方法swap中的两个参数数据类型不同,则可以定义为下面的语句:
void swap<T,R>(T a,R b);
这里的泛型参数并不是指方法的参数,而是用来定义类型的参数,也可称作类型参数。在泛型类和方法的定义中,类型参数是实例化时,泛型类型变量所指定的类型,是特定类型的占位符。
泛型类实际上并不是一个类型,而是一个类型的蓝图,需要在指定了尖括号<>内的类型参数后成为一个具体的类型。泛型参数有以下几个特点。
(1)类型参数可以是编译器能够识别的任何类型。
(2)可以创建任意多个不同类型的泛型类的实例。
(3)指定了类型参数后,编译器在运行时将每个T替换为相应的类型参数。
(4)泛型参数在指定后不能够更改。
泛型类和方法中,泛型参数可以不止定义一个,不同的泛型参数要使用不同的名称,泛型参数的命名通常满足以下几个特点。
(1)使用描述性的名称命名,使人明白参数含义。
(2)将T作为描述性类型参数名的前缀。
(3)在泛型参数名中指示对此泛型参数的约束。
(4)若只有一个泛型参数,使用T作为泛型参数名。
非泛型类中可以定义泛型方法,在实例化非泛型类时不需要指明类型参数,但泛型类在实例化时必须指明类型参数,并且该实例的成员必须遵循这样的类型参数,不能修改。如定义泛型类如下:
public class Num<T> { public void numshow<T>(T num) { Console.WriteLine(num); } }
则实例化时,若使用语句Num<int> num = new Num<int>();实例化类,则num对象的numshow()方法必须使用int类型。
5.5.4 类型参数的约束
编译器能够识别的类型有很多,但在定义泛型类时,可以对类型参数添加约束来限制类型参数的取值范围。对于有类型参数约束的类,使用不被允许的类型初始化,会产生编译错误。这就是本节要讲的约束。
泛型参数的范围很广,但不同的类型并不能肯定适合特定的泛型类,约束的定义能够保证指定的类型值被支持。一个类型参数可以使用一个或多个约束,并且约束自身可以是泛型类型。
类型参数的约束使用where关键字指定,但与where语句不同。参数类型的约束有6种类型,如表5-19所示。
表5-19 类型参数约束
