当前位置:编程学习 > C#/ASP.NET >>

C#中协变与逆变的个人理解

读了园子中一些前辈的关于C#中协变与逆变的文章,收获很大,分享一下我的个人理解,希望用较浅显的方式理解这个比较绕弯的概念。

 

协变与逆变应该是CLR的特性,我仅对我熟悉的C#举例说明。

说白了,它主要解决的是一个类型转换的问题,用一个最简单的泛型表达式就是:

S<T1> = S<T2>
当然这只是一个抽象的表达式,而且只包含了一个泛型类型参数,意思是将一个S<T2>的实例赋值给一个S<T1>的实例。S可能是一个接口或委托,T1和T2是有父子关系(或子父关系)的两个引用类型。在.NET4.0之前,这样的直接转换是不可能的,有时我们不得不写一些转换函数来实现。协变与逆变可以在一定程度上方便地解决这个问题。

 

我们来看看怎么理解这个表达式。

S<T1>是暴露给使用者的类型,S<T2>是真正做事的类型,所以通俗来讲,作为使用者,我们交给S<T1>的参数实际是交给了S<T2>,而我们通过S<T1>拿到的返回值实际是S<T2>给我们的。

画个图来表示就是:

image

 

图虽然粗糙,说明道理即可。可以看出,如果要使表达式S<T1>=S<T2>成立,必须满足两点:

S<T1>中的所有参数都必须类型安全地转换为S<T2>中的对应参数;
S<T2>中的所有返回值都必须类型安全地转换为S<T1>的对应返回值。
当然如果参数或者返回值的类型是与泛型类型无关的类型,那么上面两条必然满足。而且需要尤其注意的是:这里的参数和返回值类型未必是泛型参数类型本身,可能就是T1,也可能是A<T1>,还可能是A<B<C<T1>>>等等,但无疑都要满足上面的两个条件。

 

我认为理解了上面的关系之后,协变与逆变自然可以一步步推理出来了。

假设TBase是TChild的父类,如果满足S<TBase>=S<TChild>就说S支持对T的协变,.NET4.0有个新写法S<out T>。

那么应用上面的两个规则看看会发生什么:

S<TBase>中的所有参数都必须类型安全地转换为S<TChild>中的对应参数;
如果参数类型就是T本身,那么要求TBase可以类型安全地转换为TChild,即TChild=TBase,父类怎么可能类型安全地转换为子类?所以泛型类型参数绝对不可能出现在参数列表中,否则会引发编译的错误。
如果参数类型是A<T>,那么要求A<TBase>可以类型安全地转换为A<TChild>,即A<TChild>=A<TBase>。这要求A要支持对T的逆变,即A<in T>。所以我们自然而然推出了一个重要原则:方法参数的协变-逆变互换原则(该原则取自装配脑袋的博文:html">http://www.zzzyk.com/kf/201105/90066.html)。
如果参数是更复杂的多层嵌套,那么递归套用该原则即可。
S<TChild>中的所有返回值都必须类型安全地转换为S<TBase>中的对应返回值;
如果参数类型是T类型本身,那么要求TChild可以类型安全地转换为TBase,即TBase=TChild,这个毫无疑问没问题。所以T类型可以出现在返回值的位置上。
如果参数类型是A<T>,那么要求A<TChild>可以类型安全地转换为A<TBase>,即A<TBase>=A<TChild>。这要求A支持对T的协变,即A<in T>。又一个重要原则:方法返回值的协变-逆变一致原则。
同上。
 

逆变可以采用同样的推理过程,不再详述。

协变与逆变都是我们在定义这个接口或者委托的时候规定好的,至于参数与返回值的限定都只是这个规定产生的结果。一步步缕下来思路还是比较清晰的,虽然不像TBase=TChild这样一眼可以看出,但起码有个推理的方向,实在不行只要想想这个丑陋的图片就好了。

 

只是自己的理解,如果有什么纰漏,欢迎指出。 :)

 

补充:软件开发 , C# ,
CopyRight © 2012 站长网 编程知识问答 www.zzzyk.com All Rights Reserved
部份技术文章来自网络,