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

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

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

协变 out 用在什么接口/委托上?

协变允许你用更具体的类型替换泛型参数,但前提是该参数只作为返回值出现。所以 out 只能用于输出位置:接口或委托的泛型参数如果只出现在返回类型中(比如方法返回值、只读属性的 get 访问器),才能加 out

常见可协变的接口有 IEnumerableIReadOnlyListFunc(无参返回 T 的委托)。例如:

IEnumerable strings = new List();
IEnumerable objects = strings; // ✅ 合法:string 是 object 的子类,且 IEnumerable 的 T 是 out

但不能用于 List 这种可变集合——它有 Add(T item),T 出现在输入位置,不满足协变条件。

逆变 in 为什么只能用于输入参数?

逆变允许你用更通用的类型替换泛型参数,但它要求该参数只作为输入参数出现。所以 in 只能用于方法参数、委托参数等“进来的”位置。

典型例子是 IComparerAction

IComparer comparer = Comparer.Default;
IComparer stringComparer = comparer; // ✅ 合法:string 是 object 的子类,且 IComparer 的 T 是 in

因为 Compare(T x, T y) 中的 T 只用来接收参数,不返回 T,所以可以安全地“向上兼容”。

注意:一个泛型参数不能同时是 inout;也不能在同一个接口里既当输入又当输出(否则编译器会报错)。

萝卜简历

萝卜简历

免费在线AI简历制作工具,帮助求职者轻松完成简历制作。

下载

自定义接口加 inout 的硬性限制

你不能随便给任意泛型接口加 inout,编译器会严格检查所有成员中的使用位置:

  • out T → 所有 T 必须出现在仅输出位置:返回类型、只读属性、委托返回值等
  • in T → 所有 T 必须出现在仅输入位置:方法参数、委托参数、ref/out 参数的类型不允许(它们是双向的,违反纯输入)

例如这个接口无法标记为 out

interface Bad {
    T Get();           // ✅ 输出
    void Set(T value); // ❌ 编译错误:T 出现在输入位置
}

委托中 FuncAction 的 in/out 已经预设好了

不必自己写 delegate 声明,.NET 内置委托已经按规则标注:

  • Func:TResult 是 out
  • Func:T 是 in,TResult 是 out
  • Action:T 是 in
  • Predicate:T 是 in
  • Comparison:T 是 in

这意味着你可以直接利用这些协变/逆变能力做类型转换,但别试图把 Action 赋给 Action——那是反的,会编译失败;正确的是 Actionaction = x => Console.WriteLine(x); Action stringAction = action;(因为 string 可安全传给期望 object 的地方)。

真正容易被忽略的是:协变和逆变只对引用类型生效。如果你用 intstruct,哪怕满足位置要求,也无法进行这种隐式转换——编译器不会报错,但转换语句根本不会通过类型检查。

https://www.php.cn/faq/2023944.html

发表回复

Your email address will not be published. Required fields are marked *