当前位置:首页 > C# > 正文

深入理解C#泛型中的协变与逆变(C#协变逆变入门与实战指南)

在C#编程中,泛型(Generics)为我们提供了类型安全和代码复用的强大能力。而C#协变(Covariance)与C#逆变(Contravariance)则是泛型高级特性中的重要概念,它们让泛型接口和委托在类型转换时更加灵活。本文将用通俗易懂的方式,带你从零开始掌握这两个概念,并通过实际代码示例加深理解。

什么是协变与逆变?

简单来说:

  • 协变(Covariance):允许你使用“更派生的类型”替代“原始类型”。例如,你可以把 IEnumerable<Cat> 赋值给 IEnumerable<Animal>,前提是 Cat 继承自 Animal
  • 逆变(Contravariance):允许你使用“更通用的类型”替代“原始类型”。例如,你可以把 Action<Animal> 赋值给 Action<Cat>
深入理解C#泛型中的协变与逆变(C#协变逆变入门与实战指南) C#协变  C#逆变 泛型协变逆变 C#泛型教程 第1张

协变(Covariance)详解

协变使用 out 关键字声明,表示该泛型参数只能作为输出(返回值),不能作为输入参数。

来看一个例子:

// 定义基类和派生类class Animal { }class Cat : Animal { }class Program{    static void Main()    {        // 创建一个 Cat 的列表        var cats = new List<Cat> { new Cat(), new Cat() };        // 协变:IEnumerable<T> 是协变的(定义为 IEnumerable<out T>)        IEnumerable<Animal> animals = cats; // ✅ 合法!        foreach (var animal in animals)        {            Console.WriteLine(animal.GetType().Name);        }    }}

这里之所以能成功,是因为 IEnumerable<T> 接口被定义为:

public interface IEnumerable<out T>

注意 out 关键字,它告诉编译器:这个泛型参数 T 只用于返回值,不会被修改,因此可以安全地进行“向上转型”。

逆变(Contravariance)详解

逆变使用 in 关键字声明,表示该泛型参数只能作为输入(方法参数),不能作为返回值。

看下面的例子:

class Animal { }class Cat : Animal { }class Program{    static void Feed(Animal animal)    {        Console.WriteLine("喂动物!");    }    static void Main()    {        // Action<T> 是逆变的(定义为 Action<in T>)        Action<Cat> feedCat = Feed; // ✅ 合法!        feedCat(new Cat()); // 调用时传入 Cat,但 Feed 方法接收 Animal    }}

这是因为 Action<T> 委托被定义为:

public delegate void Action<in T>(T obj);

由于 T 只作为输入参数,传入一个 Cat 给接受 Animal 的方法是安全的(因为 CatAnimal 的子类)。

自定义协变与逆变接口

你也可以自己定义支持协变或逆变的泛型接口:

// 协变接口:只能返回 Tinterface IProducer<out T>{    T Get();}// 逆变接口:只能接收 Tinterface IConsumer<in T>{    void Consume(T item);}// 实现class AnimalProducer : IProducer<Animal>{    public Animal Get() => new Animal();}class CatConsumer : IConsumer<Cat>{    public void Consume(Cat cat) => Console.WriteLine("吃猫粮");}// 使用IProducer<Animal> producer = new AnimalProducer();IProducer<Cat> catProducer = null; // ❌ 不能反过来!协变只支持“向上”IConsumer<Cat> consumer = new CatConsumer();IConsumer<Animal> animalConsumer = consumer; // ✅ 逆变:Animal 更通用,可赋值给 Cat 消费者

注意事项与限制

  • 协变(out)和逆变(in)**仅适用于接口和委托**,不适用于类。
  • 使用 out 的泛型参数不能出现在方法参数中(只能作为返回值)。
  • 使用 in 的泛型参数不能作为返回值(只能作为参数)。
  • 引用类型才支持协变/逆变,值类型(如 intstruct)不支持。

总结

通过本文,你应该已经掌握了 C#协变C#逆变 的基本原理和使用场景。它们是 C#泛型教程 中不可或缺的高级特性,能让你写出更灵活、更安全的代码。记住:

  • out → 协变 → 只出不进 → 用于返回值
  • in → 逆变 → 只进不出 → 用于参数

掌握这些知识后,你在阅读 .NET 框架源码或设计自己的泛型 API 时,将更加得心应手!

希望这篇关于 泛型协变逆变 的教程对你有帮助!