C#协变概念困惑:参数传递场景是否误用协变?
理清泛型协变的这个常见误区
嘿,这个问题真的是泛型变体里很容易搞混的点,我来帮你拆解清楚~
首先,咱们先回到你的例子代码,先搞明白为什么这段代码能编译通过:
// 该方法接受任意引用类型的IEnumerable Int32 Count(IEnumerable<Object> collection) { ... } ... // 调用时传入IEnumerable<String> Int32 c = Count(new[] { "Grant" });
这里的核心不是方法参数的“逆变”,而是**IEnumerable<T>本身是协变泛型接口**——它的定义是public interface IEnumerable<out T>,泛型参数T用out修饰,这就意味着它是协变的。
那协变到底允许什么?你之前的理解有一点偏差,咱们重新理清楚:
- 协变(
out关键字):针对泛型接口/委托,允许你把Generic<Derived>类型的实例,安全地转换为Generic<Base>类型来使用——这里的Derived是Base的子类。之所以安全,是因为协变的泛型参数T只能出现在输出位置(比如接口里方法的返回值)。拿IEnumerable<T>来说,你只能从它里面取出T类型的元素,而取出的string(Derived)肯定能赋值给object(Base),完全没有类型安全问题。
那为什么你会觉得这是逆变场景?因为你把“方法参数的输入”和“泛型类型的变体转换”搞混了:
你看到的是把IEnumerable<string>传给了接受IEnumerable<object>的方法,但本质上是先把IEnumerable<string>协变转换成了IEnumerable<object>,再传给方法——这个转换本身是协变允许的,和方法参数是“输入位置”没有冲突。
接下来再纠正你对逆变/协变定义的小偏差:
- 逆变(
in关键字):不是“泛型类型参数可从类转为其派生类”,而是泛型类型的实参可以从基类转为派生类——比如IComparer<in T>,你可以把IComparer<object>的实例当作IComparer<string>来使用。因为逆变的T只能出现在输入位置(比如接口里方法的参数):当你需要一个能比较string的比较器,用一个能比较object的比较器完全没问题,因为string是object的子类,能被后者处理。 - 协变(
out关键字):你的这个理解是对的——泛型类型实参可从派生类转为基类,比如IEnumerable<string>转IEnumerable<object>,因为T只出现在输出位置,转换后取出的元素依然安全。
最后再总结一下,避免以后混淆:
- 泛型的协变/逆变是针对泛型接口/委托本身的,判断的是这个泛型类型能不能在不同的实参类型之间安全转换;
- 协变(
out):Generic<Derived>→Generic<Base>,安全,因为T只出输出位置; - 逆变(
in):Generic<Base>→Generic<Derived>,安全,因为T只出输入位置。
内容的提问来源于stack exchange,提问作者qqqqqqq
相关产品推荐
相关产品推荐

