协变(out)仅允许T出现在返回值位置,确保类型参数只“出”不“入”,如IEnumerable<T>;逆变(in)仅允许T出现在输入参数位置,如IComparer<T>;未标注的泛型默认不变,无隐式转换。

协变(out)只允许用在返回值位置
协变不是“让任意泛型类型都能向上转型”,它只在编译器能静态验证「该类型参数不会被写入」时才生效。比如 IEnumerable<T> 支持协变,是因为它的所有公开方法(如 GetEnumerator()、GetEnumerator().Current)都只把 T 当作返回值,从不接收 T 类型的输入参数。
一旦你在协变接口里加一个 void Add(T item) 方法,编译器立刻报错:Invalid variance: The type parameter 'T' must be contravariantly valid on 'IProducer<T>.Add(T)'. 'T' is covariant.
-
out不是语法糖,是编译器强制执行的安全契约:你声明了out T,就等于向编译器承诺「这个T永远只出来,不进去」 - 常见协变接口:
IEnumerable<T>、IReadOnlyList<T>、Func<TResult>—— 注意这些接口里T都只出现在返回位置 - 别试图给
List<T>或ICollection<T>做协变赋值:它们本身不是协变接口,且内部有可变操作,强行转型会破坏类型安全
逆变(in)只允许用在参数输入位置
逆变的本质是「放宽输入约束」。比如 IComparer<object> 可以安全地用于比较 string 列表,因为所有 string 都是 object,传给 Compare(object x, object y) 完全没问题。
但反过来不行:IComparer<string> 不能用于 List<object>,因为 Compare(string x, string y) 无法处理非字符串对象,运行时可能抛 InvalidCastException。
-
in关键字禁止你在接口中把T用作返回类型,否则编译报错:Invalid variance: The type parameter 'T' must be covariantly valid on 'IConsumer<T>.Get()'. 'T' is contravariant. - 典型逆变接口:
IComparer<T>、IEqualityComparer<T>、Action<T>—— 它们的T全部只出现在参数列表里 - 注意委托的逆变行为只适用于参数,不适用于泛型委托实例本身的字段或属性;例如
Action<string> a = (s) => Console.WriteLine(s.Length);赋值给Action<object>合法,但反过来赋值会编译失败
不变(invariant)才是泛型默认行为
没加 in 或 out 的泛型类型,比如 List<T>、Dictionary<K,V>、Func<T, TResult>(注意:这里 T 是输入,TResult 是输出),全部是不变的。这意味着 List<string> 和 List<object> 完全没有继承关系,哪怕 string 继承自 object。
- 不变性最安全,也最严格:它不提供任何隐式转换,避免所有因读/写混用导致的类型漏洞
- 不要误以为「只要类型兼容就能协变」——
Func<string, int>不能赋给Func<object, int>,因为第一个参数是输入位置,而Func的T参数未标in,它是不变的 - 若真需要类似效果,得自己定义带
in的委托,例如delegate int MyFunc<in T>(T input);
协变逆变失效的典型场景
即使接口声明了 out 或 in,实际使用时仍可能因上下文破坏变体规则而失败。最常见的是「泛型类型嵌套」和「实现类暴露非变体成员」。
- 嵌套泛型会丢失变体信息:
IEnumerable<IComparable<string>>不能转成IEnumerable<IComparable<object>>,因为IComparable<T>的T是逆变的(in),但外层IEnumerable的协变无法穿透到内层泛型实参 - 实现类若公开了违反变体的方法,会导致协变/逆变引用无法安全调用:例如一个类实现
IProducer<out T>,但又额外提供了void Set(T value),那么通过IProducer<object>引用调用时,编译器无法阻止你传入非string实例,从而绕过类型检查 - 数组虽支持协变(
string[]→object[]),但这是不安全的,运行时写入错误类型会抛ArrayTypeMismatchException;而泛型协变是编译期完全安全的,这点务必分清


















