逆变和协变的解释
逆变和协变都是针对模板类/接口中的参数类型来说的。
假定一个父类Father
, 一个子类Child
, 一个模板类SampleTemplate<T>
简单来说
- 协变(covariant): 需要
SampleTemplate<Father>
的地方,可以传入SampleTemplate<Child>
。常见的协变类型:IEnumberable
- 逆变:需要
SampleTemplate<Child>
的地方,可以传入SampleTemplate<Father>
。常见的逆变类型:List<Child>.Sort
函数使用的IComparer
理解
面向对象编程中,很容易理解,需要父类的地方,都可以使用子类。
反之需要子类的地方,却不能使用父类。
所以协变看起来似乎理所当然,而逆变看起来有点反逻辑。
这里首先看一下,如何定义一个模板类是否支持逆变或者协变。
- 协变:
SampleTemplate<out T>
- 逆变:
SampleTemplate<in T>
这里的in
和out
是什么意思?
结合前面的IEnumerable<out T>
和IComparer<in T>
:
- 协变:在
IEnumberable
中,如果一个函数需要IEnumberable<Father>
作为入参,我们传IEnumerbale<Child>
也可以,因为我们使用这个IEnumerable
时,是从其中获取元素,作为Father
处理,所以IEnumerable
中的数据是出
向的,对应out
。所以传入Child
类,输出Father
类没有问题。 - 逆变:当
List<Child>
类的Sort调用IComparer
时,Sort函数签名要求的IComparer<Child>
,实际表明它会向IComparer
传入一个Child
类型,而这个类型会在IComparer
内部使用,于是对应in
入方向。自然只要实际实现的模板类,能接受Child
类型即可,实际上也是相当于子类到父类的转换。
所以逆变和协变,对应的都是子类向父类的转换(子类也不可能向父类转换),不同点在于协变是模板类可以接收子类参数而输出父类;而逆变是模板类本身是作为被输入的实体,由外部输入子类,模板类把他当作父类使用。