协方差和反演 – 只是调用保证基类行为的不同机制?

我正在努力理解这两个概念。 但我想在经过许多video和SO QA之后,我将其简化为最简单的forms:

协变 – 假设一个子类型可以做它的基类型。
逆变 – 假设您可以像对待基类型一样处理子类型。

假设这三个类:

class Animal { void Live(Animal animal) { //born! } void Die(Animal animal) { //dead! } } class Cat : Animal { } class Dog : Animal { } 

协变

任何动物都可以做动物做的事。
假设子类型可以执行其基类型的操作。

 Animal anAnimal = new Cat(); anAnimal.Live(); anAnimal.Die(); Animal anotherAnimal = new Dog(); anotherAnimal.Live(); anotherAnimal.Die(); 

逆变

你可以对动物做任何事情,你可以做任何动物。
假设您可以像处理其基类型一样处理子类型。

 Action kill = KillTheAnimal; Cat aCat = new Cat(); KillTheCat(kill, aCat); Dog = new Dog(); KillTheDog(kill, aDog); KillTheCat(Action action, Cat aCat) { action(aCat); } KillTheDog(Action action, Dog aDog) { action(aDog); } void KillTheAnimal(Animal anAnimal) { anAnimal.Die(); } 

它是否正确? 似乎在一天结束时,协方差和逆变允许你做的只是使用你自然期望的行为,即每种类型的动物都具有所有动物特征,或者更普遍 – 所有子类型都实现了它们的所有特征。基类型 。 似乎它只是允许显而易见的 – 它们只支持不同的机制,允许您以不同的方式获得inheritance的行为 – 一个从子类型转换为基本类型(协方差),另一个从基类型转换为子类型-type(Contravariance),但在其核心,两者都只是允许调用基类的行为。

例如,在上面的例子中,你只是考虑到AnimalCatDog子类型都有LiveDie的方法 – 它们很自然地从它们的基类Animalinheritance。

在这两种情况下 – 协方差和逆变 – 我们允许调用保证的一般行为,因为我们确保在从特定基类inheritance时调用行为的目标。

在Covariance的情况下 ,我们隐式地将一个子类型转换为它的基类型并调用基类型行为(如果基类型行为被子类型覆盖则无关紧要……点是,我们知道它存在)。

在Contravariance的情况下 ,我们采用一个子类型并将它传递给一个我们知道只调用基类型行为的函数(因为base-type是forms参数类型),所以我们可以安全地转换基类型到子类型。

我正在努力理解这两个概念。

是的,你是。 很多人都这样做。

但我想在经过许多video和SO QA之后,我将其简化为最简单的forms:

你还没有。

协方差意味着子类型可以执行其基类型所做的事情。

不,这就是Liskov替代原则。

逆变法意味着您可以像处理其基本类型一样处理子类型。

不,那只是重新陈述你所说的协方差。

协方差和逆变的真正升华是:

  • 协变转换保留了另一种转换的方向。

  • 逆变换转换了另一次转换的方向。

Dog可以转换为AnimalIEnumerable可以转换为IEnumerable 。 方向被保留,因此IEnumerable是协变的。 IComparable可以转换为IComparable ,它可以反转转换的方向,因此它是逆变的。

我在数学上理解协方差的含义,所以我猜在compsci中它是一样的。

需要明确的是:数学家使用“方差”来表示一堆不同的东西。 数学和计算机科学共同的含义是范畴理论的定义。

在C#中,这只是支持这两种关系的位置和方式的问题?

在数学上,方差告诉您关系是由映射保留还是反转。 如果我们有映射T --> IEnumerable并且关系“可以转换为通过身份或引用转换”那么情况就是在C#中,如果X与Y相关,则IEIE 。 因此,映射是关于该关系的协变。

这些function试图通过支持它们来实现什么?

人们经常要求“我有一种采取一系列动物的方法,我手头有一系列的乌龟;为什么我必须将序列复制到一个新的序列才能使用这种方法?” 这是一个合理的请求,我们经常得到它,并且在LINQ使得使用序列更容易之后我们更频繁地得到它。 这是一个通常有用的function,我们可以以合理的成本实现,所以我们实现了它。

方差 – 指复杂类型(数组,列表,委托,generics)与其基础类型的子类型方向的关系。

换句话说,它是关于允许隐式地转换复杂类型的方向。

两个复杂类型(委托)根据其基础类型Animal和Cat的关系示例。

对于子类型方向, 协方差是隐式转换的保留方向( Animal <-Cat

 // Covariance based on type of return param of delegate var catDelegate = new Func(delegate {return null;}); // Allowed implicit casting from delegate based on Cat return param // to delegate based on Animal return param Func animalDelegate = catDelegate; 

逆变量是关于子类型方向的隐式转换的反向( Animal-> Cat

 // contravariance based on type of passed arguments of delegate var animalDelegate = new Action(delegate{}); // Allowed implicit casting from delegate based on Animal passed param // to delegate based on Cat passed param Action catDelegate = animalDelegate; 

不变性是一种不受支持的隐式转换(在任何方向)

通用列表是不变的

 List animals = new List(); // error! List animals = new List(); // error! 

C#中支持的方差的示例

数组是协变的

 Animal[] animals = new Cat[10]; // possible 

通用IEnumerable是协变的

 IEnumerable animals = new List(); // possible 

如果我们只考虑基类型和子类型以及如何调用基类型行为,我认为我们正在限制协方差和逆变的范围。 反义词和协方差的真正价值来自于什么类型的类型(Eric lippert所解释的预测http://blogs.msdn.com/b/ericlippert/archive/2009/11/30/what-s-the-difference -between-covariance-and-assignment-compatibility.aspx )可以使用它们创建。 以下关于方差的常见问题应该能够清除您的疑问。 http://blogs.msdn.com/b/csharpfaq/archive/2010/02/16/covariance-and-contravariance-faq.aspx