1.前言
根据维基百科的定义,协变与逆变是在计算机科学中,描述具有父/子型别关系的多个型别通过型别构造器、构造出的多个复杂型别之间是否有父/子型别关系的用语。
初次看定义一定是一头雾水,不知道协变逆变的具体规则与实现是什么,下面我将会通过几个例子来解释什么叫做协变,什么叫做逆变。
2.协变逆变的简单认知
有字面解释来看,协变可以解释为和谐的变化,而逆变可以解释为逆反的变化,可以看出二者是相对应的关系。协变、逆变这一对关系首先出现在数学、物理领域,所以下面我将从数学的角度提供一个示例来解释协变与逆变。
在数学中可以使用比较符号 > 来比较两个数字的大小,比如 3 > 2 是始终成立的,假设存在两个变量 x 和 y 来存储两个数值,现在定义一个 Double 方法 (x) => ( x * 2 ) (简化为Lambda表达式),可以很容易的得到以下推导式:
x > y => Double(x) > Double(y)
以上推导式是始终成立的,在推导式的左右两侧,比较符始终为 > 符号,这种比较符不变的投影就可以看作是协变的,它保留了之前的次序关系;
再定义一个 Negative 方法 (x) => ( -x ),可以得到以下推导式:
x > y => Negative(x) < Negative(y)
以上推导式也是始终成立的,但是在推导式的右侧比较符变为了 < 符号,这种比较符变为相反的投影可以看作是逆变的,它反转了之前的次序关系;
现在,再定义一个 Squared 方法 (x) => ( x * x ),以下推导式并不是始终成立的:
x > y => Squared(x) > Squared(y)
or
x < y => Squared(x) < Squared(y)
当 x = 0, y = -1 时,其比较符反转,而当 x = 1, y = 2 时,其比较符不变,在这种情况下这种投影可以看作是不变的,它无法保证是否反转次序。
3.C#中的协变逆变
根据上面的例子我们就可以大致地了解了什么是协变和逆变,下面就要转到 C# 中来讲了,在 C# 中这种是否反转地过程可以类比到是否兼容,比如 string 类型兼容于 object 类型,string数组化后依然兼容于object数组,那么可以看出数组是支持协变的,但是协变可能会导致类型不安全,如下例子:
object[] array = new string[10];
// error
// array[0] = 10;
这里已经将 array 声明为 string[] 类型,无法再接受 int 类型。
委托类型也支持协变。如下代码,string兼容于object,返回值为string的fun兼容于返回值为object的委托。