泛型
泛型(generic)为.NET 框架引入了类型参数(type parameters)的概念。
泛型实现了类型和方法的“参数化”,就像在普通的方法调用中,经常要用参数来告诉它们使用什么值。同样,泛型类型和方法也可以让参数告诉它们使用什么类型。
泛型带来的好处非常像静态语言较之动态语言的优点:更好的编译时检查,更多在代码中能直接表现的信息,更多的IDE 支持,更好的性能。
泛型有两种形式:泛型类型(包括类、接口、委托和结构——没有泛型枚举)和 泛型方法。
类型参数(type parameter)是真实类型的占位符。在泛型声明中,类型参数要放在一对尖括号内,并以逗号分隔。如 Dictionary<TKey,TValue> ,类型参数是 TKey 和 TValue 。当使用泛型类型或方法时,要用真实的类型代替。如 Dictionary<string, int>。这些真实的类型称为类型实参(type argument)。
如同 类型不是对象而是对象的模板,同样,泛型类型也不是类型,而是类型的模板。
判读泛型声明:
Converter<int, double> converter = TaskSquareRoot; // Converter<int, double> 委托,输入int,输出double
List<double> doubles = testInt.ConvertAll<double>(converter); // List<TOutput> ConvertAll<TOutput>(Converter<T,TOutput> conv)
static double TaskSquareRoot(int x)
{
return Math.Sqrt(x);
}
.NET 中的泛型集合
最基础的泛型集合接口为 IEnumerable,表示可迭代的项的序列。IEnumerable 可以请求一个 IEnumerator 类型的迭代器。由于分离了可迭代序列和迭代器,这样多个迭代器可以同时独立地操作同一个序列。如果从数据库角度来考虑,表就是 IEnumerable,而游标是 IEnumerator。
泛型的类型约束(type constraint)用于进一步控制可指定的类型实参。
- 引用类型约束 <T> where T : class
- 值类型约束 <T> where T : struct
- 构造函数类型约束 <T> where T : new()
- 转换类型约束 :允许你指定另一个类型,类型实参必须可以通过一致性、引用或装箱转换隐式地转换为该类型。
就像实例字段从属于一个实例一样,静态字段从属于声明它们的类型。对于泛型,每个封闭类型都有它自己的静态字段集。
JIT 编译器如何处理泛型
对于所有不同的封闭类型,JIT 的职责就是将泛型的 IL 转换成本地代码,使其能真正运行起来。
使用 List<T> 作为例子。JIT 为每个值类型实参 (int, long, float) 都创建不同的本地代码,为引用类型 (string, stream 等) 共享相同的本地代码。之所以可以这样是因为所有引用都具有相同的大小,对其操作也是一样的。
假设一个 ArrayList 和一个 List<byte>,它们分别包含相同的6个值。
先来看看 ArrayList,假定使用的是一个32位 CLR 每个已装箱的字节都要产生8字节的对象开销,另加4字节(本来是1字节,但要向上取整到一个字的边界)用于数据本身。除此之外,引用本身也要消耗4字节。所以,每个有效数据都要花费至少16字节。除此之外,缓冲区中还要为引用准备一些额外的未使用的空间。
把它和 List<byte> 进行比较。列表中的每个字节都占用元素数组中一个字节的空间。缓冲区仍有“浪费”的空间,它们等着由新添加的项使用--但最起码,每个未使用的元素只会浪费一个字节。
泛型迭代
在 C#1 中,为了使用 foreach,集合要么必须实现 System.Collections.IEnumerable 接口,要么必须有一个类似于返回一个类型的 GetEnumerator() 方法,而且该类型含有一个恰当的 MoveNext() 方法和 Current 属性。
泛型在 C# 和其他语言中的限制
为什么不能将 List<string> 转换成 List<object> ?
泛型可变性的缺乏
1. 泛型为何不支持协变性
在左侧的代码中,animals 实际引用的对象是一个 Cat[] ;(第二句会在运行时报错:System.ArrayTypeMismatchException:“尝试访问类型与数组不兼容的元素。”)
在右侧的代码中,animals 实际引用的是一个 List<Cat>。(第一句连编译都不会通过)(单看第二句其实是可以运行的 (一个 Dog 类当然能放到一个动物列表中))
两者都只要求存储对 Cat 实例的引用。左侧的数组版本虽然可以通过编译,但执行时一样会失败。且泛型的设计者认为,这比编译时就失败还要糟糕——静态类型的全部意义就在于在代码运行之前找出错误。
说明:那么,为什么数组是协变的?因为 .Net 第一版允许支持从 Java 中编译来的代码。因为 Java 有,所以 .Net 也就有了。
2. 协变性在什么时候有用
实际上,当 someType 只描述返回类型参数的操作时,协变就是安全的;而当 someType 只描述接受类型参数的操作时,逆变就是安全的。
(someType 代词,某种类型,可替换为 IEnumerable<T>)