大家通常会犯一个错误,就是将派生类型的委托分配给基类型委托的变量,下面来看这个主题叫做可变性,它分为三种——协变、逆变、不变。

协变

如果类型参数只用做输出值与构造委托有效性之间的常数关系叫做协变。
你也许知道,你可以将派生类型的对象赋值给基类型的变量,这称作类型兼容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
internal class Program {
static void Main(string[] args) {
Factory<Cat> catMaker = MakeCat;
Factory<Animal> animalMaker = catMaker;//报错
}
static Cat MakeCat() {
Cat cat = new Cat();
return cat;
}
}
delegate T Factory<T>();
class Animal {
public int NumberOfLegs = 4;
}
class Cat:Animal { }

出现以上问题的原因很简单,Cat虽然是Animal的子类,但是Factory<Cat>不是Factory<Animal>的子类,必然无法赋值。
如果我们想用这样的赋值,就需要告诉编译器我要使用协变。
使用协变的前提:类型参数只用做输出值与构造委托有效性之间的常数关系,必须使用out关键字标记委托声明中的类型参数。

1
delegate T Factory<out T>();

可能会觉得协变的定义很难懂,接下来我来解释一下什么叫输出值。
输出值就是返回值,上面的MakeCat函数的返回值是一个Cat类型的对象,Factory<Animal>接收一个Animal类型的返回值,用Animal来接受返回值为Cat的对象,并声明成out协变保证这个派生类对象只用来当做函数返回值,这样当然没问题。

逆变

逆变用于在期望传入基类时允许传入派生类对象的特性称作逆变。
如果上面的函数变成这样

1
2
3
4
5
6
7
8
9
10
11
12
internal class Program {
static void Main(string[] args) {
Factory<Animal> animalMaker = MakeAnimal;
Factory<Cat> CatMaker = animalMaker;
}
static void MakeAnimal(Animal animal) {}
}
delegate void Factory< T >(T t);
class Animal {
public int NumberOfLegs = 4;
}
class Cat:Animal { }

这次我们用一个MakeAnimal函数接受一个Animal类或其派生类的对象,当我们传入了Cat时,虽然Cat是Animal的派生类,但是Factory<Cat>与Factory<Animal>平级,所以不能直接赋值。
如果就想这样做,就需要告诉编译器我要使用逆变。使用协变的前提:类型参数只用做输入值与构造委托有效性之间的常数关系,必须使用in关键字标记委托声明中的类型参数。

1
delegate T Factory<in T>();

MakeAnimal函数接受一个animal的对象,我们传入了animal的派生类对象并声明成in逆变保证这个派生类对象只用来当做函数传参,这样做当然没问题

注意事项

可变性处理的是可以使用基类型替换派生类型的安全情况,反之亦然。因此可变性只适用于引用类型。

in和out关键字的显式变化只适用于委托和接口,不适用于类、结构、方法。

不包括in和out关键字的委托和接口类型参数是不变的。这些类型参数不能用于协变或逆变。