协变和逆变都是术语,前者指能够使用比原始指定的派生类型的派生程度更大(更具体的)的类型,后者指能够使用比原始指定的派生类型的派生程度更小(不太具体的)的类型。 泛型类型参数支持协变和逆变,可在分配和使用泛型类型方面提供更大的灵活性。
协变 convariance
样例01
//基类
class Animal{
public int Legs=4;
}
//派生类
class Dog:Animal{
}
delegate T Factory<T>();
class Program{
static Dog MakeDog{
return new Dog();
}
static void Main(){
Factory<Dog> dog=MakeDog;
Factoru<Animal> animal=dog;//这里编译会报错
Console.WriteLine(animal().Legs.ToString());
}
}
上述代码编译器会报错,Main 方法第二行,提示不能隐式把右边类型转换为左边类型。
Dog 是 Animal 的派生类,但委托 Factory<Dog> 没有从 Factory<Animal> 派生。相反,两个委托对象是同级的,它们都是 delegate 类型派生。
再仔细分析一下,我们可以看到如果类型参数只用做输出值,则同样的情况也适用于任何泛型委托。对于所有这样的情况,我们应该可以使用派生类创建委托类型,这样应该能够正常工作,因为调用代码总是期望得到一个基类的引用,这也正是它会得到的。
如果派生类只是用于输出值,那么这种结构化的委托有效性之间的常数关系叫协变。 为了让编译器知道这是我们的期望,必须使用 out
关键字标记委托生命中的类型参数。
为该实例中的委托增加 out
关键字,代码就可以通过编译。
delegate T Factory<out T>();
逆变 contravariance
样例02
//基类
class Animal{
public int Legs=4;
}
//派生类
class Dog:Animal{
}
delegate void Factory<in T>(T a);
class Program{
static void ShowLegs(Animal a){
Console.WriteLine(a.Legs);
}
static void Main(){
Factory<Animal> animal=ShowLegs;
Factoru<Dog> dog=animal;
dog(new Dog());
}
}
和之前的情况相似,默认情况下不可以赋值两种不兼容的类型。但是和之前情况也相似的是,有一些情况可以让这些赋值生效。
这种在期望传入基类时允许传入派生对象的特性叫做逆变。 可以在类型参数中显式的使用 in
关键字来表示。
delegate void Factory<int T>(T a);
接口中的协变和逆变
现在你应该已经理解了协变和逆变可以应用到委托上。其实相同的原则也可以应用到接口上,可以在声明接口的时候使用 out
和 in
关键字。
样例03
class Animal { public string Name; }
class Dog : Animal {};
interface IMyIfc<out T>{
T GetFirst();
}
class SimpleReturn<T>:IMyIfc<T>{
public T[] items=new T[2];
public T GetFirst(){ return items[0]; }
}
class Program{
static void DoSomething(IMyIfc<Animal> returner){
Console.WriteLine(returner.GetFirst().Name);
}
static void Main(){
SimpleReturn<Dog> dog=new SimpleReturn<Dog>();
dog.items[0]=new Dog(){ Name="hashiqi" };
IMyIfc<Animal> animal=dog;
DoSomething(animal)
}
}