17.6 泛型类

在前面的示例代码中已经见到过了泛型类,我们现在关注一下泛型类的创建过程:

❑确定将类中的哪些类型通用化为类型参数;

❑使用一个具有通用描述的标识符作为类名称,在类名称后面的尖括号中,放置类型参数,多个类型参数使用逗号分隔,类型参数命名请参阅17.4节;

❑在类的声明主体中使用之前定义的类型参数;

❑考虑对类型参数应用约束。

其中,需要注意的是,类型参数的数量和代码的通用性(即代码可重用性)成正比,但过度的通用会影响代码的可用性,以致让其他开发人员难以阅读和理解,因此需要掌握通用性和可用性之间的平衡。

我们在17.5节讲了类型参数的约束,一个有益的规则是,应用尽可能多的约束,但有个前提,必须保证可以处理所有需要处理的类型的前提下。例如,前文中的Stack<T>类型,如果对T的约束太多,比如类型实参只能是int型,那么其他需要处理的string、bool等类型就被挡在门外了,这影响了该泛型类型的通用性,属于约束过于严格了。因此,约束的应用要恰当。例如,当我们知道一个泛型类只会使用引用类型作为类型实参,那么就应当对该类型实参应用类约束,防止该泛型被意外地用于值类型。

构造类型这个概念前面已经提到过了,这里再做进一步的阐述,如图17-8所示。

17.6 泛型类 - 图1

图 17-8 泛型类型和构造类型

构造类型用于引用泛型类型,而且构造类型分为两种:

❑使用一个或多个类型形参的构造类型称为开放构造类型。

❑不使用类型形参的构造类型称为封闭构造类型。

使用Stack<T>{}泛型类为例,Stack<T>即为开放构造类型,而Stack<string>则为封闭构造类型。接下来,探讨泛型类的继承和可访问性。

首先,我们需要澄清两个概念:

❑封闭式构造类型:引用的泛型类型指定了类型实参,此时创建的是封闭式构造类型;

❑开放式构造类型:类型参数处于未指定状态(例如在指定泛型基类时),此时创建的是开放式构造类型。

上述概念使用图示进行阐述,如图17-9所示。

接下来,我们学习下泛型类和继承相关的问题上的几点规则,如下:

❑泛型类可以从非泛型类、封闭式构造类型或开放式构造类型继承,例如:


class BaseClass{}

class BaseClassGeneric<T>{}

//泛型类ClassOne<T>继承自非泛型基类BaseClass

class ClassOne<T>:BaseClass{}

//封闭式构造类型示例

class ClassTwo<T>:BaseClassGeneric<int>{}

//开放式构造类型示例

class ClassThree<T>:BaseClassGeneric<T>{}


17.6 泛型类 - 图2

图 17-9 开放式构造类型和封闭式构造类型

❑非泛型类可以从封闭式构造类型继承,但无法从开放式构造类型或裸类型参数继承,因为在运行时客户端代码无法提供实例化基类所需的类型实参,例如:


class BaseClass{}

class BaseClassGeneric<T>{}

//正确

class ClassOne:BaseClassGeneric<int>{}

//错误

//class ClassTwo:BaseClassGeneric<T>{}

//错误

//class ClassThree:T{}


❑开放式构造类型继承的泛型类,如果基类的某个类型参数没有在派生类中使用,则必须在基类中为这个类型参数提供类型实参,例如:


class BaseClassGeneric<T,U>{}

//正确

class ClassOne<T>:BaseClassGeneric<T,int>{}

//正确

class ClassTwo<T,U>:BaseClassGeneric<T,U>{}

//错误

//class ClassThree<T>:BaseClassGeneric<T,U>{}


❑从开放式构造类型继承的泛型类如果有约束,那么泛型类也必须指定约束,并且泛型类的约束是基类型约束的超集或包含基类型约束,例如:


class BaseClass<T>where T:System.IComparable<T>,new(){}

class ClassOne<T>:BaseClass<T>where T:System.IComparable<T>,new(){}


❑开放式构造类型和封闭式构造类型可以用做方法参数,例如:


void Copy<T>(List<T>list1,List<T>list2)

{

//……

}

void Move(List<int>list1,List<int>list2)

{

//……

}


注意 泛型类是不变的。也就是说,如果输入参数指定List<BaseClass>,当你试图提供List<ChildClass>时,将会发生编译时错误。