- A+
C# 泛型
泛型允许开发人员创建算法和模式,并为不同数据类型重用代码
定义简单泛型类
在类名之后,需要在一对尖括号中指定类型参数
public class Stock<T> { private T[] InternalItems { get; } public void Push(T data) { ... } public void Pop() { ... } }
设计规范
要为类型参数选择有意义的名称,并为名称附加“T”前缀,例如
EntityCollection<TEntity>
考虑在类型参数的名称中指明约束
泛型约束
泛型允许为类型参数定义约束
,强迫作为类型实参提供的类型遵守各种规则。
接口约束
public interface IPerson { string Name { get; set; } }
public class Stock<T> where T : IPerson { public Stock(T data) { data.Name = "张三"; } }
添加了接口约束后,编译器会确保每次使用Stock类的时候,所提供的类型参数都实现了IPerson接口。可以显示访问接口成员
类类型约束
public class Stock<T> where T : EntityBase
与接口约束相似,要求所有类型实参都能隐式转换为EntityBase类。同样可以显式调用约束类成员
与接口约束不同的是不允许出现多个类类型约束,因为不可能从多个不同的类派生
struct/class 约束
约束泛型类型必须struct或者class类型
where T : class //T必须是一个类(class)类型
where T : struct //T必须是一个类struct类型
如果约束了泛型类型struct类型,可空值同样会不符合条件
构造函数约束
并非所有对象都肯定有公共默认构造函数,编译器不允许未约束的类型参数调用默认构造函数。因此,可以通过在其他所有约束之后添加new()
。这就是所谓的构造函数约束,它要求实参必须有默认构造函数
public class EntityDictionary<TKey,TValue> where TKey : new() { TKey key = new TKey(); }
只能对默认构造函数进行约束。不能为有参构造函数指定约束
多个约束
可为任意类型参数指定任意数量的接口类型约束,但类类型约束只能指定一个。where
后面的所有约束都以逗号分隔。如果有多个类型参数,则每个类型参数前面都要使用where
关键字
public class EntityDictionary<TKey,TValue> where TKey : IComparable<TKey>,IFormattable where TValue : EntityBase,new()
注意,两个where子句之间并不存在逗号
泛型方法
与泛型类一样,泛型方法要使用泛类型参数。在泛型或非泛型类型中都能声明泛型方法。在泛型类型中声明,其类型参数要和泛型类型的类型参数有区别
泛型方法声明
通过在方法名之后添加类型参数来声明泛型方法
public class Cat { public void Say<T>(T entity) where T : IComparable { } }
指定约束
泛型方法的类型参数可使用同泛型类的类型参数同样的方式指定约束,可以约束类型参数必须实现某个接口,或者是某个类的子类
public class Cat { public void Say<T>(EntityBase<T> entity) where T : IComparable { } }
注意:如果参数类型是由泛型类组成,那么泛型类上的约束 泛型方法也必须同样声明
public class EntityBase<T> where T : IComparable { public T Key { get; set; } }
泛型继承
泛型类型参数与它们的约束都不会被派生类所继承(因为泛型类型参数不知类的成员),但是派生类的类型参数必须具有等同或更强于基类的约束
internal class EntityDictionary<TKey,TValue>:Dictionary<TKey,TValue> where TKey : IComparable<TKey> where TValue : EntityBase<TKey>,new() { }
error:类型
TKey
不能用作泛型类型或方法EntityBase<T>
中的类型参数“T”。没有从TKey
到System.IComparable
的装箱转换或类型参数转换。
泛型方法的继承
当虚泛型方法被继承并重写时或显示实现接口方法时,约束是隐式继承的,不可以重新声明
public class EntityBase<T> where T : IComparable { public virtual void Run<T>(T t) { } }
public class Base:EntityBase { public override void Run<T>(T t) where T :IComparable //error:重写和显式接口实现方法的约束是从基方法继承的,因此不能直接指定这些约束,除非指定 "class" 或 "struct" 约束。 { base.Run(t); } }
协变性 与逆变性
协变性
假定两个类型X和Y具有特殊关系,即每个X类型的值都能转换成Y类型。如果I<X>
和I<Y>
也具有同样的关系,那么称为“I<X>对I<Y>
协变”
由于I<X>
和I<Y>
并不是父子类型所以无法进行自由转换
List<Cat> cat = new List<Cat>(); List<Animal> animal = new List<Cat>();//error:无法将类型“System.Collections.Generic.List<ConsoleApp.Cat>”隐式转换为“System.Collections.Generic.List<ConsoleApp.Animal>”
不过,从C#4.0 起使用out类型参数修饰符可以允许协变性
public interface IEntity<out T> { public T Build(); }
public class Entity<T> : IEntity<T> { public T Build() {...} }
internal class Program { static void Main(string[] args) { IEntity<Cat> entity = new Entity<Cat>(); IEntity<Animal> animal = new Entity<Cat>(); } }
协变转换存在一些重要限制
- 只有泛型接口和泛型委托才可以协变。泛型类和结构永远不是协变的
- 被out修饰的参数类型只能做方法返回值,不能做参数
- 提供给“来源”和“目标”的类型实参必须是引用类型
逆变性
协变性的反方向即被成为逆变性 ,我们可以通过in
修饰符,将接口标记为逆变
class Fruit { } class Apple: Fruit { } class Orange : Fruit { } interface ICompareThings<in T> { bool Compare(T x, T y); }
class FruitCompare : ICompareThings<Fruit> { public bool Compare(Fruit x, Fruit y) {...} }
public static void Main(string[] args) { ICompareThings<Fruit> compare = new FruitCompare(); Apple apple1 = new Apple(); Apple apple2 = new Apple(); Orange orange = new Orange(); //因为Apple和Orange都是Fruit的子类所以可以进行比较 compare.Compare(apple1, apple2); compare.Compare(apple1, orange); //将ICompareThings<Fruit>逆变成ICompareThings<Apple>类型 ICompareThings<Apple> ac = compare; //此时由于泛型类型变成了Apple,因此Orange无法再参与比较 ac.Compare(apple1, orange);//error:无法从“ConsoleApp.Orange”转换为“ConsoleApp.Apple” }