协变(Covariance) 和 逆变(Contravariance)
协变(Covariance) 和 逆变(Contravariance) 是面向对象编程中关于类型系统的一种概念,主要用于处理泛型类型、接口或委托的继承和类型转换问题。它们决定了在继承结构中,泛型参数如何与类型或接口的继承关系保持一致或相反。
1. 协变(Covariance)
协变指的是,当你有一个泛型类型 G<T>
,如果 TDerived
是 TBase
的子类,那么 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>
,因为Dog
是Animal
的子类,协变允许这个类型转换。IProducer<T>
使用了out
,这表明它是协变的。
协变的场景
协变一般用于方法的返回类型,即子类实例可以作为基类的返回结果,主要用于只输出数据的场景,比如:
- 迭代器
- 泛型委托(如
Func<T>
)
2. 逆变(Contravariance)
逆变是指当你有一个泛型类型 G<T>
,如果 TDerived
是 TBase
的子类,那么 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>
,因为Animal
是Dog
的基类,逆变允许这个类型转换。IConsumer<T>
使用了in
,这表明它是逆变的。
逆变的场景
逆变一般用于方法的参数类型,允许基类参数接受派生类对象,主要用于只输入数据的场景,比如:
- 事件处理器
- 泛型委托(如
Action<T>
)
3. 协变与逆变的总结
- 协变(Covariance,使用
out
):用于返回类型允许子类替代基类。适用于只读场景(如返回值)。 - 逆变(Contravariance,使用
in
):用于参数类型允许基类替代子类。适用于只写场景(如方法参数)。
图解协变和逆变
-
协变:
G<TDerived>
可以当作G<TBase>
Dog
是Animal
的子类,因此IProducer<Dog>
可以赋值给IProducer<Animal>
。
-
逆变:
G<TBase>
可以当作G<TDerived>
Animal
是Dog
的父类,因此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# 中,使用
out
和in
关键字来分别实现协变和逆变。 - 在 C++ 中,虽然没有直接的关键字,但可以通过返回类型协变等机制实现类似的效果。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本