c# - 协变和逆变 - 调用有保证的基类行为的不同机制?

标签 c# covariance contravariance

我很难理解这两个概念。但我认为在观看了许多视频和 SO QA 之后,我将其提炼为最简单的形式:

协变 - 假设子类型可以做其基本类型所做的事情。
逆变 - 假设您可以像对待其基本类型一样对待子类型。

假设这三个类:

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<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();
}

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

例如,在上面的例子中,您只是考虑了 AnimalCatDog 子类型这两个事实有方法 LiveDie - 它们很自然地继承自它们的基类 Animal

在这两种情况下 - 协变和逆变 - 我们都允许调用有保证的一般行为,因为我们已经确保调用行为的目标继承自特定基类。

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

在逆变的情况下,我们采用子类型并将其传递给我们知道仅调用基类型行为的函数(因为基类型是形式参数类型),所以我们可以安全地将基本类型转换为子类型。

最佳答案

差异 - 指的是复杂类型(数组、列表、委托(delegate)、泛型)如何与其基础类型的子类型化方向相关。

换句话说,它是关于在什么方向上允许隐式转换复杂类型。

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

协方差 是关于子类型化方向(动物<-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;

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

// 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;

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

通用列表是不变的

List<Animal> animals = new List<Cat>(); // error!
List<Cat> animals = new List<Animal>(); // error!

C# 中支持的变体示例

数组是协变的

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

泛型 IEnumerable 是协变的

IEnumerable<Animal> animals = new List<Cat>(); // possible

关于c# - 协变和逆变 - 调用有保证的基类行为的不同机制?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19262845/

相关文章:

Python Sklearn 协方差矩阵对角线条目不正确?

c# - 参数必须是输入安全错误

c# - 使用公钥证书激活产品

c# - 获取 xml 属性的值作为字符串

c# - 将 .NET Core 与旧版 .NET 框架 dll 一起使用

generics - 需要澄清关于 `Box` 、 `Vec` 和其他集合的(协)方差的 Rust Nomicon 部分

c# - 数据绑定(bind)到 xaml 中对象列表中的对象列表中的对象列表

java泛型协方差

java - 通配类型的协变和逆变

c# - 将子类分配给接口(interface),涉及泛型和协变