C# 委托协变和逆变 C# delegate的in和out泛型修饰符如何使用

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

委托协变:用
out
修饰返回类型,允许子类转父类

协变发生在委托的返回值上。当你声明一个返回

string
的委托,却想用它指向返回
object
的方法时,编译器会报错——除非你在泛型参数上加
out
。因为
string
object
的子类,而“更具体的类型能安全当更宽泛的类型用”就是协变的本质。

典型场景是统一处理不同具体类型的工厂或转换逻辑:

Func<t></t>
内置委托本身就用了
out T
,所以
Func<string></string>
可赋值给
Func<object></object>
自定义委托必须显式加
out
,且该参数只能出现在返回位置(不能作参数、不能在泛型约束中被用作基类)
错误写法:
public delegate T MyFunc<t>() where T : class</t>
—— 没有
out
就不支持协变,
MyFunc<string></string>
无法转成
MyFunc<object></object>

委托逆变:用
in
修饰参数类型,允许父类转子类

逆变作用于委托的输入参数。比如你有一个接受

object
的委托,但实际想传入只处理
string
的方法——这看似危险,实则安全:只要方法能处理更宽泛的输入,那传更具体的子类当然没问题。这时就得靠
in

常见于事件处理器、比较器、谓词等以输入为主的操作:

Action<t></t>
Predicate<t></t>
都用了
in T
,所以
Action<object></object>
可接收
Action<string></string>
in
参数只能出现在形参位置,不能用于返回值、不能参与
where T : SomeBase
这类约束(否则协变/逆变会被禁用)
若委托同时有输入和输出,可混合使用:
Func<in t out r></in>
,.NET 中的
Func<t tresult></t>
就是这样设计的

为什么加了
in
/
out
还编译失败?常见限制条件

协变和逆变不是万能的,C# 对其施加了严格的类型安全性检查。即使语法正确,以下情况仍会拒绝转换:

泛型参数出现在「非协变/逆变位置」:比如
out T
却在方法体内把
T
当字段类型存储,或作为
ref
/
out
参数传递
存在装箱/拆箱隐式转换干扰:比如
Func<int></int>
不能转成
Func<object></object>
,因为
int
是值类型,
object
是引用类型,这不是继承关系而是装箱,
out
不覆盖此规则
委托签名不匹配:协变只影响返回类型,参数数量、顺序、名称、是否
params
等仍需完全一致
跨程序集时,目标框架版本太低(如 .NET Framework 4.0 之前不支持泛型变体)

实际调试时怎么快速判断该用
in
还是
out

别记规则,看数据流向。打开你的委托定义,盯住那个泛型参数

T

如果
T
只出现在返回值位置(或
yield return
get
访问器),就用
out T
如果
T
只出现在参数位置(包括
ref
以外的普通参数、
params T[]
),就用
in T
如果
T
同时出现在参数和返回值里(比如
T DoSomething(T input)
),那它既不能协变也不能逆变,不能加任何修饰符
VS 提示 “cannot convert … because the type parameter … is not valid variance position” 就说明你放错地方了

真正容易被忽略的是:变体只对泛型委托类型生效,对具体委托实例(如

new Func<string>(...)</string>
)没意义;而且一旦用了
in
/
out
,该参数在委托体内就变成只读(协变返回值不可作为输入,逆变参数不可作为返回),这是编译器强制的保护机制。

相关推荐