我很难理解协方差和逆方差之间的区别。
发布于 2010-02-05 02:58:33
问题是“协方差和逆方差有什么区别?”
协方差和逆方差是将集合的一个成员与另一个成员相关联的映射函数的属性。更具体地说,映射相对于集合上的关系可以是协变的,也可以是逆变的。
考虑所有C#类型集合的以下两个子集。首先:
{ Animal,
Tiger,
Fruit,
Banana }.第二,这是一个明显相关的集合:
{ IEnumerable<Animal>,
IEnumerable<Tiger>,
IEnumerable<Fruit>,
IEnumerable<Banana> }存在从第一集合到第二集合的映射操作。也就是说,对于第一集合中的每个T,第二集合中的对应类型是IEnumerable<T>。或者,简而言之,映射是T → IE<T>。注意,这是一个“细箭头”。
到目前为止和我在一起?
现在让我们考虑一个关系。第一个集合中的类型对之间存在赋值兼容性关系。类型为Tiger的值可以赋值给类型为Animal的变量,因此这些类型被称为“赋值兼容”。让我们以更简短的形式编写“可以将类型为X的值分配给类型为Y的变量”:X ⇒ Y。请注意,这是一个“胖箭头”。
因此,在我们的第一个子集中,以下是所有赋值兼容性关系:
Tiger ⇒ Tiger
Tiger ⇒ Animal
Animal ⇒ Animal
Banana ⇒ Banana
Banana ⇒ Fruit
Fruit ⇒ Fruit在支持某些接口的协变赋值兼容性的C# 4中,第二个集合中的类型对之间存在赋值兼容性关系:
IE<Tiger> ⇒ IE<Tiger>
IE<Tiger> ⇒ IE<Animal>
IE<Animal> ⇒ IE<Animal>
IE<Banana> ⇒ IE<Banana>
IE<Banana> ⇒ IE<Fruit>
IE<Fruit> ⇒ IE<Fruit>请注意,映射T → IE<T>保留了赋值兼容性的存在和方向。也就是说,如果是X ⇒ Y,那么IE<X> ⇒ IE<Y>也是真的。
如果我们在一个胖箭头的两边都有两个东西,那么我们可以用对应的细箭头的右边的东西来替换两边的东西。
对于特定关系具有此属性的映射称为“协变映射”。这应该是有意义的:在需要动物序列的地方可以使用Tiger序列,但反之亦然。在需要老虎序列的地方,不一定要使用动物序列。
这是协方差。现在考虑所有类型集合的这个子集:
{ IComparable<Tiger>,
IComparable<Animal>,
IComparable<Fruit>,
IComparable<Banana> }现在我们有了从第一个集合到第三个集合T → IC<T>的映射。
在C# 4中:
IC<Tiger> ⇒ IC<Tiger>
IC<Animal> ⇒ IC<Tiger> Backwards!
IC<Animal> ⇒ IC<Animal>
IC<Banana> ⇒ IC<Banana>
IC<Fruit> ⇒ IC<Banana> Backwards!
IC<Fruit> ⇒ IC<Fruit>也就是说,映射T → IC<T>保留了存在,但颠倒了赋值兼容性的方向。也就是说,如果是X ⇒ Y,那么就是IC<X> ⇐ IC<Y>。
保持但反转关系的映射称为逆变映射。
同样,这显然应该是正确的。可以比较两个动物的设备也可以比较两个老虎,但是可以比较两个老虎的设备不一定可以比较任何两个动物。
这就是C# 4中协方差和逆方差的区别。协方差保留了可赋值的方向。逆方差反转它。
发布于 2010-02-02 22:12:09
举例子可能是最简单的--我当然也是这样记住它们的。
协方差
典型示例:IEnumerable<out T>、Func<out T>
您可以将IEnumerable<string>转换为IEnumerable<object>,或将Func<string>转换为Func<object>。值只从这些对象中产生。
它之所以有效,是因为如果您只从API中取出值,并且它将返回特定的值(如string),则可以将该返回值视为更通用的类型(如object)。
Contravariance
典型示例:IComparer<in T>、Action<in T>
您可以将IComparer<object>转换为IComparer<string>,或将Action<object>转换为Action<string>;值仅进入这些对象。
这一次它是有效的,因为如果API需要通用的东西(比如object),你可以给它更具体的东西(比如string)。
更一般地说是
如果你有一个接口IFoo<T>,它在T中可以是协变的(即,如果T只在接口中的输出位置(例如,返回类型)使用,则将其声明为IFoo<out T> )。如果T仅用于输入位置(例如,参数类型),则它在T (即IFoo<in T>)中可以是逆变量。
它可能会让人感到困惑,因为“输出位置”并不像听起来那么简单-- Action<T>类型的参数仍然只在输出位置使用T --如果你明白我的意思,Action<T>的逆变就会把它扭转过来。它是一种“输出”,因为值可以从方法的实现传递到调用者的代码,就像返回值一样。幸运的是,这类事情通常不会出现:)
发布于 2011-07-15 04:02:14
我希望我的帖子能帮助你对这个话题有一个与语言无关的看法。
对于我们的内部培训,我使用了一本很棒的书"Smalltalk,Objects and Design (Chamond Liu)“,我改写了下面的例子。
“一致性”是什么意思?其思想是设计具有高度可替代类型的类型安全类型层次结构。如果您使用的是静态类型语言,那么获得这种一致性的关键是基于子类型的一致性。(我们将在这里高层次地讨论Liskov替换原理(LSP)。)
实际示例(伪码/C#中的无效):
Egg anEgg = aBird.Lay();引用aBird可以合法地被鸟或鸭子实例替代。我们说返回类型与定义了Lay()的类型是协变的。子类型的重写可以返回更加专门化的类型。他们提供more.”aPiano.Play(aPianist); aPiano不能被钢琴或GrandPiano实例合法地替换!GrandPiano只能由Virtuoso演奏,钢琴家太一般了!GrandPianos必须可以由更通用的类型播放,然后播放是一致的。我们说参数类型与定义Play()的类型是逆变的。子类型的重写可以接受更通用的类型。=>“他们需要的更少。”回到C#:
因为C#基本上是一种静态类型语言,所以必须显式地标记应该是协变或逆变的类型接口的“位置”(例如,参数和返回类型),以保证该类型的一致使用/开发,从而使其正常工作。在动态类型语言中,LSP一致性通常不是问题,换句话说,如果您只在类型中使用动态类型,则可以完全消除.Net接口和委托上的协变和逆变“标记”。-但这不是C#中的最佳解决方案(您不应该在公共接口中使用dynamic )。
回到理论上来:
所描述的一致性(协变返回类型/逆变参数类型)是理论上的理想(由语言Emerald和POOL-1支持)。一些oop语言(例如Eiffel)决定应用另一种类型的一致性,例如。还有协变参数类型,因为它比理论理想更好地描述了现实。在静态类型语言中,通常必须通过应用“双重调度”和“访问者”等设计模式来实现所需的一致性。其他语言提供所谓的“多分派”或多方法(这基本上是在运行时选择函数重载,例如使用CLOS),或者通过使用动态类型获得所需的效果。
https://stackoverflow.com/questions/2184551
复制相似问题