我很难理解这两个概念。但我认为经过许多视频和 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(逆变),但在其核心,两者都只是允许调用基类的行为。
例如在上面的例子中,你只是考虑了一个事实,即两者Cat
的Dog
子类型Animal
都有方法Live
和Die
- 它们很自然地从它们的基类继承Animal
。
在这两种情况下 - 协变和逆变 - 我们都允许调用一般行为,这是有保证的,因为我们确保调用行为的目标继承自特定的基类。
在 Covariance 的情况下,我们隐式地将子类型转换为其基类型并调用基类型行为(如果基类型行为被子类型覆盖并不重要......重点是,我们知道它存在)。
在逆变的情况下,我们将一个子类型传递给一个我们知道只会调用基类型行为的函数(因为基类型是形参类型),所以我们可以安全地转换基类型到一个子类型。