协变out用于只含输出位置(如返回值、只读属性)的泛型接口或委托,如IEnumerable、Func;逆变in用于只含输入位置(如方法参数)的泛型接口或委托,如IComparer、Action。

协变 out 用在什么接口/委托上?
协变允许你用更具体的类型替换泛型参数,但前提是该参数只作为返回值出现。所以 out 只能用于输出位置:接口或委托的泛型参数如果只出现在返回类型中(比如方法返回值、只读属性的 get 访问器),才能加 out。
常见可协变的接口有 IEnumerable、IReadOnlyList、Func(无参返回 T 的委托)。例如:
IEnumerablestrings = new List (); IEnumerable
但不能用于 List 这种可变集合——它有 Add(T item),T 出现在输入位置,不满足协变条件。
逆变 in 为什么只能用于输入参数?
逆变允许你用更通用的类型替换泛型参数,但它要求该参数只作为输入参数出现。所以 in 只能用于方法参数、委托参数等“进来的”位置。
典型例子是 IComparer 和 Action:
IComparer
因为 Compare(T x, T y) 中的 T 只用来接收参数,不返回 T,所以可以安全地“向上兼容”。
注意:一个泛型参数不能同时是 in 和 out;也不能在同一个接口里既当输入又当输出(否则编译器会报错)。
自定义接口加 in 或 out 的硬性限制
你不能随便给任意泛型接口加 in 或 out,编译器会严格检查所有成员中的使用位置:
- 加
out T→ 所有T必须出现在仅输出位置:返回类型、只读属性、委托返回值等 - 加
in T→ 所有T必须出现在仅输入位置:方法参数、委托参数、ref/out参数的类型不允许(它们是双向的,违反纯输入)
例如这个接口无法标记为 out:
interface Bad{ T Get(); // ✅ 输出 void Set(T value); // ❌ 编译错误:T 出现在输入位置 }
委托中 Func 和 Action 的 in/out 已经预设好了
不必自己写 delegate 声明,.NET 内置委托已经按规则标注:
-
Func:TResult 是out -
Func:T 是in,TResult 是out -
Action:T 是in -
Predicate:T 是in -
Comparison:T 是in
这意味着你可以直接利用这些协变/逆变能力做类型转换,但别试图把 Action 赋给 Action——那是反的,会编译失败;正确的是 Action(因为 string 可安全传给期望 object 的地方)。
真正容易被忽略的是:协变和逆变只对引用类型生效。如果你用 int、struct,哪怕满足位置要求,也无法进行这种隐式转换——编译器不会报错,但转换语句根本不会通过类型检查。









