C# 泛型
1. 泛型的意义
类型安全(更好的编译时检查),性能增强(避免装箱和拆箱),代码复用(减少了功能相似的代码)
2. 种类:
- 泛型类型(包括类,结构,接口,委托,数组)
- 泛型方法。
枚举、属性、构造函数、字段 这些没有对应的泛型
3. 约束 :
- where T: class (必须是引用类型)
- where T:struct (必须是值类型,nullable 类型的值类型除外)
- where T: 基类名 (限制类型参数必须是继承自指定的基类,或是基类本身)
- new() (必须有无参构造函数,只能用于引用类型(如果类型是结构类型也不会报错),并且必须放在约束的最后如
class Sample
)where T : class, IDisposable, new() - where T:接口名 (限制类型参数必须是实现指定接口)
- where T:U (为 T 提供的类型参数必须是为 U 提供的参数或派生自为 U 提供的参数。这称为裸类型约束.)
- 组合约束
上面的几种约束可以形成组合约束。若一个类型参数有多个约束,约束之间用
,
分开; 如果包含多个类型参数 ,多个类型参数用where
分开。如public class KeyValue
where TKey : new() where TValue: Base,new()
4. 相关术语:
- 形参(一般用 paramter 描述)
- 实参(一般用 argument 描述)
- 类型参数:是一种形参,如
List
中的T
- 类型实参 ,如
List
中的int
- 泛型的 开放类型
指的是带有 类型参数(且未分配类型实参) 的类型,如Type myType= typeof(List
中的变量 myType;) ; - 泛型的 封闭类型; 已经分配具体 类型实参 的类型,如
Type myType2= typeof(List
中的变量 myType2;) ;
5. 泛型方法
-
带有类型参数的方法不一定都是泛型方法
public class Animal
{ public void Move1(T speed) { } public void Move2 (S speedT) { } }Move2
是泛型方法 ,Move1
则不是. 判断一个方法是否是泛型方法,可以通过反射获取到MethodInfo
对象,然后调用 对象的IsGenericMethod
方法. -
方法重载: 调用方法时,如果有多个方法签名相匹配,则优先调用类型参数较少的方法
-
类型参数的省略:调用泛型方法时 ,如果是下面的2种情况则可以省略类型参数
- 使用类型明确的值,传给被调用的方法时 如
Move
(100); // 可以简写成 Move(100); - 使用泛型方法调用另一个具有相同方法签名的泛型方法时
public void Move
(T speed){} public void MoveWrapper (T s) { Move (s);// 可以简写成 Move(s); }
6. 协变和逆变:
-
协变和逆变指的是泛型的类型实参之间的类型转换 。且协变和逆变都是类型安全的.
C# 只对 接口 和 委托 提供可变性支持(包括协变和逆变),另外支持
数组
的协变(但是仅仅是编译时不报错,允许时访问到内部的具体元素还是会出现运行时异常,这样设计的原因据说是为了兼容 Java)。 类型参数前加in
或out
,这种写法是 C# 4.0 才引人的, 这也表明之前的 C#版本不支持协变和逆变 -
协变(Covariance 允许使用比原始指定的类型派生程度更大的类型)
通用的表示是
Ixxx<父类或父接口> iBase = new xxx<子类或子接口>();
,其中
Ixxx
接口中必然有类型参数
前面带有out
关键字修饰
如:IEnumerable
animals= new List ();
当泛型的类型参数只描述返回类型参数(输出)的操作时,协变是类型安全的;
若类型参数前加 out ,表明该类型只能作为函数的返回类型使用,不能作为输入 -
逆变(Contravariance 允许使用比原始指定的类型派生程度更小的类型)
通用的表示是
Ixxx<子类或子接口> iBase = new xxx<父类或父接口>();
其中Ixxx
接口中必然有类型参数
前面带有in
关键字修饰。
如:Action
b = (target) => { Console.WriteLine(target.GetType().Name); }; Action d = b; d(new Derived()); 当类型参数只描述接受类型参数(输入)的操作时,逆变就是安全的 。
若类型参数前加 in ,表明该类型只能作为函数的输入类型使用,不能作为输出类型。 -
不变体(Invariance 只能使用原始指定的类型;泛型类型参数既不是协变类型,也不是逆变类型) 如:
List
cats= new List ();
7. 进阶内容
- 泛型类型内的静态字段:
不同的封闭类型具有不同的静态字段
- JIT 编译器如何处理泛型
- JIT 为每个以值类型作为类型实参的 封闭类型 都创建不同的代码
- 所有使用引用类型作为类型实参的 封闭类型 都共享相同的本机代码
这是因为所有引用类型的变量实际上只是指向堆上对象的指针.而所有的指针都可以使用相同的方式操作.