协变和逆变
1.前⾔
根据的定义,协变与逆变是在计算机科学中,描述具有⽗/⼦型别关系的多个型别通过型别构造器、构造出的多个复杂型别之间是否有⽗/⼦型别关系的⽤语。
初次看定义⼀定是⼀头雾⽔,不知道协变逆变的具体规则与实现是什么,下⾯我将会通过⼏个例⼦来解释什么叫做协变,什么叫做逆变。
2.协变逆变的简单认知
有字⾯解释来看,协变可以解释为和谐的变化,⽽逆变可以解释为逆反的变化,可以看出⼆者是相对应的关系。协变、逆变这⼀对关系⾸先出现在数学、物理领域,所以下⾯我将从数学的⾓度提供⼀个⽰例来解释协变与逆变。
在数学中可以使⽤⽐较符号 > 来⽐较两个数字的⼤⼩,⽐如 3 > 2 是始终成⽴的,假设存在两个变量 x 和 y 来存储两个数值,现在定义⼀个 Double ⽅法 (x) => ( x * 2 ) (简化为Lambda表达式),可以很容易的得到以下推导式:
x > y => Double(x) > Double(y)
以上推导式是始终成⽴的,在推导式的左右两侧,⽐较符始终为 > 符号,这种⽐较符不变的投影就可以看作是协变的,它保留了之前的次序关系;
再定义⼀个 Negative ⽅法 (x) => ( -x ),可以得到以下推导式:
c++string类型
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的委托。
public delegate object mydelege();
static string fun2()
{
return"";
}
static void Main(string[] args)
{
mydelege md1 = fun2; //string兼容于object,返回值为string的fun兼容于返回值为object的委托
}
Framework4.0后,⽀持协变的接⼝有很多。IEnumerable<T>、IEnumerator<T>、IQueryable<T> 和 IGrouping<TKey, TElement>
委托类型⽀持逆变。如下代码:
class Father{ }class Son : Father{ }class Program{
public delegate void mydelege1(Father f);
public delegate void mydelege2(Son s);
static void fun1(Father s)
{
}
static void fun2(Son s)
{
}
static void Main(string[] args)
{
Father f = new Father();
Son s = new Son();
f = s;//ok,⼉⼦可以赋值给⽗亲
mydelege1 md1 = fun2;//error,输⼊参数不⽀持协变,⼉⼦类型的⽅法不能赋值给⽗亲类型的委托
mydelege2 md2 = fun1;//ok,⽗亲类型的⽅法可以赋值给⼉⼦类型的委托,逆变了。
}
}
View Code
对于⼀个委托mydelege1(Father f),定义的输⼊参数类型是Father。可以看到son是可以赋值给father的,⽽经过委托定义这样⼀个投影,发现son类型为参数的⽅法不能赋值给father类型为参数的委托。即经过这样的委托投影,son类型⽅法⽆法赋值给father类型委托了。
⽽相反的,father类型为参数的⽅法却可以赋值给son类型为参数的委托。即经过这样的委托投影,father类型的⽅法可以赋值给son类型的委托了,因此也就是逆变了。简单的来看,即投影前Son可以赋值给Fahter,投影(转成委托类型)后Father可以赋值给Son,是⼀种逆变。
为什么转成委托后,Son类型的⽅法不能赋值给Father类型的委托了,很简单,这个⽅法要接受的是Son的⽅法要处理的⾃然是Son类型的值,⽽Father为参数的委托可能接受Daughter类型的参数(假设Daughter和Son并列的继承了Father)。
因此Son⽅法就⽆法处理了。因此不允许这样操作。换成代码来说,假设这样的代码合法了:
mydelege1 md1 = fun2;//假设是合法的,
那么 md1(new Daughter())的代码要处理的时候,肯定⽆法处理了。所以,不允许存在这样的协变,⽽只允许逆变。通过上⾯的⼀些例⼦,可以看出对于委托,协变只存在与返回值中,逆变只存在与输⼊值中。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。