.NET 4.0 C# 泛型协变与反变详解
本文详细介绍 .NET 4.0 中 C# 泛型类型的协变(covariant)和反变(contravariant)特性。重点讲解 `in` 和 `out` 关键字在泛型接口中的应用规则、方法参数与返回值的相互影响机制,以及值类型的限制条件。
背景:理解协变与反变的本质
可变性(Variance)源于类型间的绑定关系。以数组为例,若类型 T 继承自类型 U,则数组 T[] 也可以隐式转换为 U[],这种同向转换称为协变(Covariant)。
在泛型中,由于泛型类/结构体不支持协变,我们通过泛型接口来实现:
- 协变(Covariant):子类引用转换为父类引用(如
IChild转为IParent),方向相同。 - 反变(Contravariant):父类引用转换为子类引用(如
IParent转为IChild),方向相反。
.NET 4.0 泛型协变与反变实战
为了解决泛型接口直接使用导致的类型不安全(例如将父类传给仅接收子类的参数),.NET 4.0 引入了 in 和 out 修饰符来明确限定类型参数的作用域。
第1步:声明协变接口(用于返回值)
当类型参数仅作为方法的返回值出现时,该参数支持协变。使用 out 关键字标记:
interface ICo<out T>
{
// T 只能出现在返回值位置
T Method();
}
此时,ICo<string> 可安全地视为 ICo<object>。
第2步:声明反变接口(用于方法参数)
当类型参数仅作为方法的输入参数出现时,该参数支持反变。使用 in 关键字标记:
interface IContra<in T>
{
// T 只能出现在参数位置
void Method(T param);
}
此时,IContra<object> 可安全地视为 IContra<string>。
第3步:混合使用 In/Out 并调用
一个接口可以同时包含 in 和 out 参数。例如:IBar<in T1, out T2> 对 T1 反变,对 T2 协变。调用时需同时考虑两者的转换:
// IBar<string, object> 可以转为 IBar<object, string>
// 因为 string -> object (协变), object -> string (反变)
核心难点:互换原则与一致原则
这是开发者最容易感到困惑的地方。尽管使用了 in 和 out,但它们在方法签名内部并非孤立存在。
第4步:遵循“协变-反变互换原则”处理参数
结论:如果一个接口 T 是协变的(out),那么它所包含的所有方法的参数类型必须是对 T 具有反变能力的类型。反之亦然。
示例说明:
interface IFoo<in T> { }
// 错误示例:IBar 声明为 in T,其内部方法 Test(IFoo<T> foo) 无法编译
// 因为 T 要传递给 IFoo<T>,而 IFoo 是反变的,这会导致逻辑冲突
// 正确示例:IBar 声明为 out T
interface IBar<out T>
{
void Test(IFoo<T> foo); // 合法!IFoo 的反变性补偿了 IBar 的协变性
}
第5步:遵循“协变-反变一致原则”处理返回值
结论:如果一个接口需要对类型 T 进行协变或反变,那么所有方法的返回值类型也必须支持与 T 相同方向的可变性。
interface IFooCo<out T> { }
interface IFooContra<in T> { }
// 错误示例:如果 Test1() 返回的是 IFooContra<T>,编译器会报错
// 因为 IBar 要求 T 协变,但其返回值 IFooContra 是反变的,两者不一致
interface IBar<out T>
{
IFooCo<T> Test1(); // 返回类型也必须是协变的
}
关键注意事项与限制
在实际开发中使用泛型协变与反变时,请务必注意以下边界情况:
第6步:规避常见陷阱
- 仅泛型接口/委托有效:泛型类(Class)或泛型方法不支持此功能。
- 值类型不参与:由于装箱/拆箱机制的影响,值类型永远不参与协变或反变。
List<int>不能转为List<object>。 - 属性读写限制:如果属性有 `get` 和 `set`,它同时包含了参数和返回值,因此不能使用单纯的 `in` 或 `out`,必须显式区分只读(getter only)与只写(setter only)以实现对应的约束。
以上就是 .NET 4.0 中 C# 泛型协变与反变的详细内容,更多关于 C# 高级特性优化程序设计的资料请关注本站其它相关文章!
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









