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

ric*_*ard 7 c# covariance contravariance

我正在努力理解这两个概念.但我想在经过许多视频和SO QA之后,我将它简化为最简单的形式:

协变 - 假设一个子类型可以做它的基类型.
逆变 - 假设您可以像处理其基本类型一样处理子类型.

假设这三个类:

class Animal
{
    void Live(Animal animal)
    {
        //born!
    }

    void Die(Animal animal)
    {
        //dead!
    }

}

class Cat : Animal
{

}

class Dog : Animal
{

}
Run Code Online (Sandbox Code Playgroud)

协变

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

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

Animal anotherAnimal = new Dog();
anotherAnimal.Live();
anotherAnimal.Die();
Run Code Online (Sandbox Code Playgroud)

逆变

任何你可以做一个动物,你可以做任何动物.
假设您可以像处理其基类型一样处理子类型.

Action<Animal> kill = KillTheAnimal;

Cat aCat = new Cat();
KillTheCat(kill, aCat);

Dog = new Dog();
KillTheDog(kill, aDog);

KillTheCat(Action<Cat> action, Cat aCat)
{  
    action(aCat);  
}

KillTheDog(Action<Dog> action, Dog aDog)
{  
    action(aDog);  
}

void KillTheAnimal(Animal anAnimal)
{
    anAnimal.Die();
}
Run Code Online (Sandbox Code Playgroud)

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

例如,在上面的例子中,你只是允许两者CatDog子类型和子类型Animal都有方法LiveDie- 它们很自然地从它们的基类继承Animal.

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

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

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

Gen*_*ady 8

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

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

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

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

// Covariance based on type of return param of delegate
var catDelegate = new Func<Cat>(delegate {return null;});

// Allowed implicit casting from delegate based on Cat return param 
// to delegate based on Animal return param 
Func<Animal> animalDelegate = catDelegate;
Run Code Online (Sandbox Code Playgroud)

逆变是一个相反的隐式转换的方向,以亚型方向(动物- >猫)

// contravariance based on type of passed arguments of delegate
var animalDelegate = new Action<Animal>(delegate{});

// Allowed implicit casting from delegate based on Animal passed param 
// to delegate based on Cat passed param
Action<Cat> catDelegate = animalDelegate;
Run Code Online (Sandbox Code Playgroud)

Invariance是一个不受支持的隐式转换(在任何方向)

通用列表是不变的

List<Animal> animals = new List<Cat>(); // error!
List<Cat> animals = new List<Animal>(); // error!
Run Code Online (Sandbox Code Playgroud)

C#中支持的差异示例

数组是协变的

Animal[] animals = new Cat[10]; // possible
Run Code Online (Sandbox Code Playgroud)

通用IEnumerable是协变的

IEnumerable<Animal> animals = new List<Cat>(); // possible
Run Code Online (Sandbox Code Playgroud)


Eri*_*ert 7

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

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

但我想在经过许多视频和SO QA之后,我将它简化为最简单的形式:

你还没有.

协方差意味着子类型可以执行其基类型的功能.

不,那是Liskov替代原则.

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

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

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

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

  • 逆变换会逆转另一次转换的方向.

Dog可转换为Animal. IEnumerable<Dog>可转换为IEnumerable<Animal>.方向被保留,IEnumerable<T>协变也是如此.IComparable<Animal>是可转换的IComparable<Dog>,它反转了转换的方向,因此它是逆变的.

我在数学上理解协方差的含义,所以我猜在compsci中也是如此.

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

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

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

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

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