协变(Covariance) 和 逆变(Contravariance)

协变(Covariance)逆变(Contravariance) 是面向对象编程中关于类型系统的一种概念,主要用于处理泛型类型、接口或委托的继承和类型转换问题。它们决定了在继承结构中,泛型参数如何与类型或接口的继承关系保持一致或相反。

1. 协变(Covariance)

协变指的是,当你有一个泛型类型 G<T>,如果 TDerivedTBase 的子类,那么 G<TDerived> 可以被看作是 G<TBase> 的子类。这意味着协变允许你在一个方法、接口或委托中返回派生类型,而类型参数是更一般的基类。

C# 中协变的例子

在 C# 中,可以使用 out 关键字来声明协变,通常用于返回类型的泛型接口或委托。

// 声明接口 IProducer<out T> 支持协变
public interface IProducer<out T>
{
T Produce();
}
public class Animal { }
public class Dog : Animal { }
public class DogProducer : IProducer<Dog>
{
public Dog Produce() => new Dog();
}
public class Program
{
public static void Main()
{
IProducer<Animal> animalProducer = new DogProducer();
Animal animal = animalProducer.Produce(); // 协变允许泛型类型转换
}
}

在上面的例子中:

  • DogProducer 实现了 IProducer<Dog>,但可以赋值给 IProducer<Animal>,因为 DogAnimal 的子类,协变允许这个类型转换。
  • IProducer<T> 使用了 out,这表明它是协变的。

协变的场景

协变一般用于方法的返回类型,即子类实例可以作为基类的返回结果,主要用于只输出数据的场景,比如:

  • 迭代器
  • 泛型委托(如 Func<T>

2. 逆变(Contravariance)

逆变是指当你有一个泛型类型 G<T>,如果 TDerivedTBase 的子类,那么 G<TBase> 可以被看作是 G<TDerived> 的子类。逆变允许使用基类来替代派生类。

C# 中逆变的例子

在 C# 中,可以使用 in 关键字来声明逆变,通常用于泛型参数作为方法参数输入的接口或委托。

// 声明接口 IConsumer<in T> 支持逆变
public interface IConsumer<in T>
{
void Consume(T item);
}
public class Animal { }
public class Dog : Animal { }
public class AnimalConsumer : IConsumer<Animal>
{
public void Consume(Animal animal)
{
Console.WriteLine("Consuming an animal.");
}
}
public class Program
{
public static void Main()
{
IConsumer<Dog> dogConsumer = new AnimalConsumer();
dogConsumer.Consume(new Dog()); // 逆变允许泛型类型转换
}
}

在上面的例子中:

  • AnimalConsumer 实现了 IConsumer<Animal>,但可以赋值给 IConsumer<Dog>,因为 AnimalDog 的基类,逆变允许这个类型转换。
  • IConsumer<T> 使用了 in,这表明它是逆变的。

逆变的场景

逆变一般用于方法的参数类型,允许基类参数接受派生类对象,主要用于只输入数据的场景,比如:

  • 事件处理器
  • 泛型委托(如 Action<T>

3. 协变与逆变的总结

  • 协变(Covariance,使用 out:用于返回类型允许子类替代基类。适用于只读场景(如返回值)。
  • 逆变(Contravariance,使用 in:用于参数类型允许基类替代子类。适用于只写场景(如方法参数)。

图解协变和逆变

  • 协变:G<TDerived> 可以当作 G<TBase>

    • DogAnimal 的子类,因此 IProducer<Dog> 可以赋值给 IProducer<Animal>
  • 逆变:G<TBase> 可以当作 G<TDerived>

    • AnimalDog 的父类,因此 IConsumer<Animal> 可以赋值给 IConsumer<Dog>

4. C++中的协变和逆变

C++ 本身没有像 C# 中那样直接支持协变和逆变的关键字,但在继承和指针/引用类型转换中,也有类似的概念。C++ 支持协变返回类型:派生类可以覆盖基类的函数,且返回派生类的对象,而不必返回基类对象。

class Base {
public:
virtual Base* create() {
return new Base();
}
};
class Derived : public Base {
public:
Derived* create() override { // 协变返回类型
return new Derived();
}
};

在这个例子中,Derived 类覆盖了 Base 类的 create 方法,并且返回 Derived*,这是协变返回类型的一个例子。

  • 协变逆变 是用于泛型类型的继承转换的概念,协变用于输出类型,逆变用于输入类型。
  • 在 C# 中,使用 outin 关键字来分别实现协变和逆变。
  • 在 C++ 中,虽然没有直接的关键字,但可以通过返回类型协变等机制实现类似的效果。
posted @   非法关键字  阅读(308)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
点击右上角即可分享
微信分享提示