协变(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 @ 2024-09-28 13:45  非法关键字  阅读(73)  评论(0编辑  收藏  举报