C# 协变和逆变方法 C#泛型中的in和out关键字如何使用

来源:这里教程网 时间:2026-02-21 17:39:25 作者:

协变
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
,哪怕满足位置要求,也无法进行这种隐式转换——编译器不会报错,但转换语句根本不会通过类型检查。

相关推荐