协变(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++ 中,虽然没有直接的关键字,但可以通过返回类型协变等机制实现类似的效果。