协变(out)允许IEnumerable视为IEnumerable(B继承A),用于返回值;逆变(in)允许IConsumer赋给IConsumer,用于参数;二者仅适用于接口和委托,保障类型安全。

在 C# 中,协变(Covariance)和逆变(Contravariance)是泛型接口和委托中非常重要的概念,它们通过 out 和 in 关键字实现,允许更灵活的类型转换。理解这两个特性有助于写出更具扩展性和复用性的代码。
什么是协变(Covariance)?
协变允许你使用比原本指定的类型更派生的类型。换句话说,如果类 B 继承自类 A,那么支持协变的泛型接口可以将 IEnumerable 视为 IEnumerable。
协变通过在泛型类型参数前使用 out 关键字来声明。注意:只能用于返回值位置(如方法的返回类型),不能作为方法参数。
常见例子:
interface IProducer{ T Get(); }
这里 out T 表示 T 只能出现在输出位置(比如返回值)。因为只读,所以类型系统可以安全地进行向上转型。
实际应用:
string str = "hello"; IProducerstringProducer = new StringProducer(); IProducer
由于 string 是 object 的子类,且 IProducer
什么是逆变(Contravariance)?
逆变则相反:它允许你使用比原本指定的类型更基础的类型。也就是说,如果有一个接受基类的方法,它可以被用来处理派生类的对象。
逆变通过在泛型类型参数前使用 in 关键字来声明。它适用于输入参数,不能用于返回值。
例子:
interface IConsumer{ void Consume(T item); }
这里 in T 表示 T 只能用作输入参数。因为方法只接收 T 类型对象,传入其子类对象是类型安全的。
实际使用:
IConsumer
虽然 stringConsumer 声明为消费 string,但它指向的是一个能处理任意 object 的消费者,因此是安全的。
.NET 中常见的协变与逆变接口
微软在 .NET 框架中已经广泛使用了这些特性:
-
IEnumerable
:协变 —— 允许将 IEnumerable 赋给 IEnumerable -
IComparable
:逆变 —— 如果一个类型能比较 Animal,那它当然也能比较 Dog -
Action
:逆变参数 —— Action -
Func
:协变返回值 —— Func 可以当作 Func
限制与注意事项
协变和逆变不是任意可用的,有严格的规则:
- 只有接口和委托支持 in 和 out,泛型类不支持
- out T 只能出现在返回类型位置,不能作为方法参数
- in T 只能出现在参数位置,不能作为返回类型
- 数组也支持协变(如 string[] 可隐式转为 object[]),但这可能导致运行时异常(不安全),而泛型接口的协变是编译时安全的
基本上就这些。掌握 in 和 out 关键字,能让你更好地设计泛型接口,写出更自然、符合直觉的 API。关键是记住:out 用于产出,in 用于输入,就像数据流动的方向一样。










