委托协变:用 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,该参数在委托体内就变成只读(协变返回值不可作为输入,逆变参数不可作为返回),这是编译器强制的保护机制。
