逆变和协变在存在于强类型语言中,虽然很少提及,但是里面蕴含了对问题的描述。感谢和我一起讨论的人。
这里,使用C#、Scala中都包含逆变的参数声明方式。
一、逆变的定义
逆变的参数可以由指定的类型的子类型代替,Scala中的逆变声明:Function1[-A,+B] ;参数可以使用A类型或者A类的子类型。
二、协变与逆变的用途不同
1.语义
常见的地方用在Function的传入参数中,Function1[-A,+B]
输入的逆变表示“只要满足这种功能即可”,所以满足的功能,在A的子类中都有,逆变强调的是功能——“能做什么”。
顺便看下协变,输出协变,协变强调的是类型,A类型可以代替父类,如牛肉是肉。协变强调的是“是什么”。
2.刀和肉
类型:食品<-肉<-牛肉。武器<-刀<-牛肉刀。(<-表示继承关系:父类<-子类)
情景:函数FunctionX需要一把刀(强调刀的功能),会生产出肉。大致的定义为FunctionX[A,B],普通刀(刀类)会生产出普通肉(肉类),牛肉刀会生产出牛肉。
A的类型?B的类型?如何确定
A的类型可以为刀和牛肉刀,因为牛肉刀也是刀。甚至说刀的子类能够满足,从继承来讲刀的子类都是刀。
所以A的类型应该为逆变——-刀(刀和子类)
因为做出的是肉,所以B类型肯定包含肉,但不确定是牛肉。所以我们可以设定返回为B类型。
对于这个情景,我们对FunctionX的最终定义为:FunctionX[-刀,+肉]
协变呢?
我们没有看到协变,实际上在C#和Scala中,我们设定一个类型 食品 来接收FunctionX的返回值也不会报错。因为所有的返回类型在语言中都被声明为协变了,也就是说实际的定义是FunctionX[-刀,+肉]。这么做的原因是:如果我返回了一个肉,那么这个肉一定是食品,我总能用返回类型的父类型代替返回的对象。
三、另外的例子
ICompareable<in T>强调“可比较”这一功能,是逆变。
IEnumerable<out T>强调的是“可数的”类型,是协变。拿List<T>说明,List<肉>表示“我放了肉在列表里面”,可以说"我放了食品在列表里面"是没错了,即可以使用List<食品>代替。但是不能说“我放了牛肉在列表里面”,所以用List<牛肉>代替是不对的。
原文地址:http://blog.csdn.net/u012289636/article/details/46880745