前言
最近在看一些项目和框架,用了好多泛型,虽然C++模板比较熟,但是泛型却接触的比较少,所以看了CLR的泛型后打算写个文章记录下方便以后自己看。(其实和模板差不多!)
泛型
介绍
泛型是CLR和编程语言提供的一种特殊机制,它支持另一种形式的代码重用,也就是算法重用,比如泛型List类的List< T >表名它操作的是一个未指定的数据类型,到时候使用泛型类型或者方法时指定的具体数据类型称为类型实参,也就是用具体的类型实参来代替T
优点
- 源代码保护,开发人员不需要访问算法的源代码,而模板编程需要访问源代码(为什么?)
- 代码可重用性
- 类型安全,将具体的类型用于泛型算法的时候,必须与指定数据类型兼容才能用于算法,否则会有异常。
- 更佳的性能,没有泛型的时候就要用Object来模拟泛型。如果操控值类型就要进行拆装箱,会造成性能损耗和频繁的垃圾回收,也就是ArrayList和List的差别。
到底有多大的性能差别?
看书上写的是操作Int32也就是值类型的时候,泛型比object快7倍。如果都操作string引用类型的话,就差不多,因为都不用拆装箱。
开放类型和封闭类型
具有泛型类型参数的类型称为开放类型,CLR禁止构造开放类型的任何实例
比如T t = typeof(T)
为所有类型参数传递了实际的数据类型,类型就称为封闭类型
泛型类型同一性
因为泛型类型写起来比较复杂,比如
List<DataTime> dt1 = new List<DateTime>();
有些人就想偷懒,用一个非泛型类型从泛型类型派生,并制定了类型实参。
internal sealed class DateTimeList : List<DateTime>{}
//然后
DateTimeList dt1 = new DateTimeList();
虽然很方便,但是这是是错的!!这样会影响类型相等性
真正简化的语法!
相当于一个别名啦~
using DateTimeList = System.Collections.Generic.List<System.DateTime>;
逆变和协变泛型类型实参
不变量:泛型类型参数不能更改
逆变量:泛型类型参数可以从一个类更改为它的派生类,用in修饰
协变量:可以更改为它的基类,用out修饰
举个栗子
public delegate TResult Func<in T, out TResult>(T arg);
Func<Object,ArgumentException> fn1 = null;
Func<String,Exception> fn2 = fn1;//不需要显示转型
泛型方法
就一句话:优先考虑特化版本!!
约束
约束的作用是限制能指定成泛型实参的类型数量,通过限制类型的数量,可以对那些类型执行更多的操作。
举个栗子:你不指定是引用类型的话,就不能对泛型实参类型的参数进行赋值为null
主要约束
主要约束,我认为就是类型约束,主要是两种
- 类型约束
public class fan<T> where T : Stream{}
T必须是Stream类的相同类或者其子类 - 特殊约束
where T : class/struct
,如果是class约束必须是引用类型,struct约束必须是值类型。如果没有约束为class,就不能将T类型的变量设为null。如果没有约束为struct,就不能new T(),因为引用类型不一定有公共无参构造器。
次要约束
次要约束一般指接口类型,这种约束向编译器承诺类型实参实现了接口,因为能指定多个接口约束,所以类型实参必须实现了所有接口约束。
构造器约束
它向编译器承诺类型实参是实现了公共无参构造器的非抽象类型,不能同时使用struct约束,因为struct默认有共欧诺个无参构造器。
internal sealed class ConstructorConstraint<T> where T : new(){}
其他
- 将泛型类型变量设为null是非法的,除非将泛型类型约束成引用类型,但是如果你很想设一个默认值
T temp = default(T);
这是可以的,T是引用类型就设为null,值类型就设为0 - 泛型变量相互比较,如果没有约束成引用类型,不能进行比较也就是== 这种,因为值类型相互比较是非法的,除非值类型重载了 ==操作符