第十二章 泛型

目录:

12.1 FCL中的泛型

12.2 泛型基础结构

12.3 泛型接口

12.4 泛型委托

12.5 委托和接口的逆变和协变泛型类型实参

12.6 泛型方法

12.7 泛型和其他成员

12.8 可验证性和约束

泛型时CLR和编程语言提供的一种特殊机制,它支持另一种形式的代码重用,即“算法重用”。

大多数算法允许都封装在一个类型中,CLR允许创建泛型引用类型和泛型值类型,但不允许创建泛型枚举类型。CLR还允许创建泛型接口和泛型委托。方法偶尔也封装有用的算法,所以CLR允许在引用类型,值类型或接口中定义泛型方法。

定义泛型类型或方法时,为类型指定的任何变量(比如T)都称为类型参数。T是变量名,源代码能使用数据类型的任何地方都能使用T。

使用泛型类型或方法时指定的具体数据类型称为类型实参。

优势:

源代码保护:使用泛型算法的开发人员不需要访问算法的源代码。

类型安全:将泛型算法应用于一个具体的类型时,编译器和CLR能理解开发人员的意图,并保证只有与指定数据类型兼容的对象才能用于算法。

更清晰的代码:由于编译器强制类型安全性,所以减少了源代码中必须进行的强制类型转换次数,使代码易于编写和维护。

更佳的性能:CLR不需要执行装箱操作,值类型的实例以传值的方式传递。

12.1 FCL中的泛型

泛型最明显的应用就是集合类。集合类型实现了许多接口,放入集合中的对象可实现接口来执行排序和搜索等操作。

12.2 泛型基础结构

CLR添加泛型需要的工作:

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

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

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

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

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

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

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

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

12.2.1 开放类型和封闭类型

具有泛型类型参数的类型成为开发类型,CLR禁止构造开放类型的任何实例。这类似于CLR禁止构造接口类型的实例。

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

12.2.2 泛型类型和继承

泛型类型仍然时类型,所以能从其他任何类型派生。使用泛型类型并指定类型实参时,实际是在CLR中定义一个新的类型对象,新的类型对象从泛型类型派生自的那个类型派生。

12.2.3 泛型类型同一性

12.2.4 代码爆炸

使用泛型类型参数的方法在进行JIT编译时,CLR获取方法的IL,用指定的类型实参替换,然后创建恰当的本机代码(这些代码未操作指定数据类型“量身定制”)。缺点:CLR要为每种不同的方法/类型组合生成本机代码。这个现象时“代码爆炸”。

CLR内建了一些优化措施能缓解代码爆炸。假如为特定的类型实参调用了一个方法,以后再用相同的类型实参调用这个方法,CLR只会为这个方法/类型组合编译一次代码。

CLR还有另一优化,它认为所有引用类型实参都完全相同,所以代码能够共享。(所有引用类型的参数或变量实际上只是指向堆上对象的指针,所有对象指针都以相同方式操纵)

如果某个类型时值类型,CLR就必须专门为那个值类型生成本机代码。因为值类型的大小不定,CLR无法共享代码,因为可能要用不同的本机CPU指令来操纵这些值。

12.3 泛型接口

使用非泛型接口来操纵值类型会发生装箱,而且会失去编译时的类型安全性。更到内容请参考在13章接口

12.4 泛型委托

CLR支持泛型委托,目的是保证任何类型的对象都能以类型安全的方式传给回调函数。泛型委托允许值类型实例在传给给回调方法时不进行任何装箱。 

12.5 委托和接口的逆变和协变泛型类型实参

委托的每个泛型类型参数都可标记为协变量或逆变量。这样就可将泛型委托类型的变量转换为相同的委托类型(但泛型参数类型不同)。泛型类型参数:

不变量:意味着泛型类型参数不能更改。

逆变量:意味着泛型类型参数可以从一个类更改为它的某个派生类。在C#中用in关键字标记逆变量形式的泛型类型参数。逆变量泛型类型参数只出现在输入位置,比如作为方法的参数。

协变量:意味着泛型类型参数可以从一个类更改为它的某个基类。C#是用out关键字标记协变量形式的泛型类型参数。协变量泛型类型参数只能出现在输出位置,比如作为方法的返回类型。

(协变性指定返回类型的兼容性,逆变性指定参数的兼容性)

对于泛型类型参数,如果要将该类型的实参传给使用out或ref关键的方法,便不允许可变性。

12.6 泛型方法

定义泛型类,结构或方法时,类型中定义的任何地方都可引用类型指定的类型参数。类型参数可作为方法参数,方法返回值或方法内部定义的局部变量的类型使用。CLR也允许方法指定它自己的类型参数。这些参数也可以作为参数,返回值,或局部变量使用。

泛型方法和类型推断

C#编译器支持在调用泛型方法时进行类型推断。这意味着编译器会在调用泛型方法时自动推断要使用的类型。

12.7 泛型和其他成员

在C#中,属性,索引器,事件,操作符方法,构造器和终结器本身不能有类型参数。但它们能在泛型类型中定义,而且这些成员中的代码能使用类型的类型参数。

12.8 可验证性和约束

 约束机制:约束的作用是限制能指定成泛型实参的类型数量。通过限制类型的数量,可以对那些类型执行更多操作。

约束可应用于泛型类型的类型参数,也可用于泛型方法的类型参数。CLR不允许基于类型参数名称或约束来进行重载;只能基于元数(类型参数个数)对类型或方法进行重载。

重写虚泛型方法时,重写的方法必须指定相同数量的类型参数,而且这些类型参数继承在基类方法上指定的约束。事实上,根本不允许为重写方法的类型参数指定任何约束。但类型参数的名称是可以改变的。类似地,实现接口方法时,方法必须指定与接口方法等量地类型参数,这些类型参数将继承由接口方法指定地约束。

12.8.1 主要约束

类型参数可以指定零个或者一个主要约束。主要约束可以代表非密封类地一个引用类型。

指定引用类型约束时,相当于向编译器承诺:一个指定的类型实参要么时与约束类型相同的类型,要么时从约束类型派生的类型。

12.8.2 次要约束

类型参数可以指定零个或者多个次要约束,次要约束代表接口类型。这种约束向编译器承诺类型实参实现了接口。由于能指定多个接口约束,所以类型实参必须实现了所有接口约束。

还有一种次要约束称为类型参数约束,有时也称为裸类型约束。这种约束用得比接口约束少得多。它允许一个泛型类型或方法规定:指定的类型实参要么就是约束的类型,要么时约束的类型的派生类。一个类型参数可以指定零个或者多个类型参数约束。

12.8.3 构造器约束

类型参数可指定零个或一个构造器约束,它向编译器承诺类型实参是是实现了公共无参构造器的非抽象类型。where T : new()

12.8.4 其他可验证性问题

1.泛型类型变量的转型

将泛型类型的变量转型为其他类型是非法的,除非转型为与约束兼容的类型。

2.将泛型类型变量设为默认值

将泛型类型变量设为null是非法的,除非将泛型类型约束成引用类型。

3.将泛型类型变量与null进行比较

无论泛型类型是否被约束,使用==或!=操作符将泛型类型变量与null进行比较都是合法的。

4.两个泛型类型变量相互比较

如果泛型类型参数不能肯定是引用类型,对同一个泛型类型的两个变量进行比较是非法的。

5.泛型类型变量作为操作数使用

将操作符应用于泛型类型操作数会出现大量问题。不能将操作符应用于泛型类型的变量。

每天学习一丢丢
原文地址:https://www.cnblogs.com/terry-1/p/10028569.html