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