为什么 IEnumerable<T>在 C# 4 中实现协变? [英] Why was IEnumerable<T> made covariant in C# 4?

查看:29
本文介绍了为什么 IEnumerable<T>在 C# 4 中实现协变?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在 C# 的早期版本中,IEnumerable 是这样定义的:

In earlier versions of C# IEnumerable was defined like this:

public interface IEnumerable<T> : IEnumerable

从 C# 4 开始,定义是:

Since C# 4 the definition is:

public interface IEnumerable<out T> : IEnumerable

  • 是否只是为了消除 LINQ 表达式中烦人的强制转换?
  • 这不会像 C# 中的 string[] <: object[](损坏的数组方差)一样引入相同的问题吗?
  • 从兼容性的角度来看,如何添加协方差?早期的代码是否仍然适用于更高版本的 .NET 或者这里是否需要重新编译?反过来呢?
  • 以前使用此接口的代码是否在所有情况下都严格保持不变,或者某些用例现在的行为是否可能有所不同?
    • Is it just to make the annoying casts in LINQ expressions go away?
    • Won't this introduce the same problems like with string[] <: object[] (broken array variance) in C#?
    • How was the addition of the covariance done from a compatibility point of view? Will earlier code still work on later versions of .NET or is recompilation necessary here? What about the other way around?
    • Was previous code using this interface strictly invariant in all cases or is it possible that certain use cases will behave different now?
    • 推荐答案

      Marc 和 CodeInChaos 的回答很不错,但只是补充一些细节:

      Marc's and CodeInChaos's answers are pretty good, but just to add a few more details:

      首先,您似乎有兴趣了解我们制作此功能所经历的设计过程.如果是这样,那么我鼓励您阅读我在设计和实现该功能时撰写的冗长系列文章.从页面底部开始:

      First off, it sounds like you are interested in learning about the design process we went through to make this feature. If so, then I encourage you to read my lengthy series of articles that I wrote while designing and implementing the feature. Start from the bottom of the page:

      协变和逆变博文

      仅仅是为了让 LINQ 表达式中烦人的转换消失吗?

      Is it just to make the annoying casts in LINQ expressions go away?

      不,避免Cast 表达式不仅仅是只是,而是鼓励我们使用此功能的动机之一.我们意识到为什么我不能在这种采用动物序列的方法中使用长颈鹿序列?"的数量会增加.问题,因为 LINQ 鼓励使用序列类型.我们知道我们想先为 IEnumerable 添加协方差.

      No, it is not just to avoid Cast<T> expressions, but doing so was one of the motivators that encouraged us to do this feature. We realized that there would be an uptick in the number of "why can't I use a sequence of Giraffes in this method that takes a sequence of Animals?" questions, because LINQ encourages the use of sequence types. We knew that we wanted to add covariance to IEnumerable<T> first.

      我们实际上考虑过使 IEnumerable<T> 协变即使在 C# 3 中也是如此,但我们认为如果不引入整个功能供任何人使用,这样做会很奇怪.

      We actually considered making IEnumerable<T> covariant even in C# 3 but decided that it would be strange to do so without introducing the whole feature for anyone to use.

      这不会像 C# 中的 string[] <: object[](损坏的数组方差)一样引入相同的问题吗?

      Won't this introduce the same problems like with string[] <: object[] (broken array variance) in C#?

      它没有直接引入该问题,因为编译器仅在已知类型安全时才允许变化.但是,它确实保留了破碎的数组方差问题.使用协方差,IEnumerable 可以隐式转换为 IEnumerable,所以如果你有一个字符串数组序列,你可以处理它作为一个对象数组的序列,然后你会遇到和以前一样的问题:你可以尝试将 Giraffe 放入该字符串数组并在运行时得到一个异常.

      It does not directly introduce that problem because the compiler only allows variance when it is known to be typesafe. However, it does preserve the broken array variance problem. With covariance, IEnumerable<string[]> is implicitly convertible to IEnumerable<object[]>, so if you have a sequence of string arrays, you can treat that as a sequence of object arrays, and then you have the same problem as before: you can try to put a Giraffe into that string array and get an exception at runtime.

      如何从兼容性的角度添加协方差?

      How was the addition of the covariance done from a compatibility point of view?

      小心.

      较早的代码是否仍可用于 .NET 的较新版本,还是需要在此处重新编译?

      Will earlier code still work on later versions of .NET or is recompilation necessary here?

      只有一种方法可以找出答案.尝试一下,看看有什么失败!

      Only one way to find out. Try it and see what fails!

      如果 X != Y,尝试强制针对 .NET X 编译的代码针对 .NET Y 运行通常是一个坏主意,无论类型系统如何更改.

      It's often a bad idea to try to force code compiled against .NET X to run against .NET Y if X != Y, regardless of changes to the type system.

      反过来呢?

      同样的答案.

      某些用例现在有可能会有所不同吗?

      Is it possible that certain use cases will behave different now?

      绝对的.使之前不变的接口协变在技术上是一个重大变化".因为它会导致工作代码中断.例如:

      Absolutely. Making an interface covariant where it was invariant before is technically a "breaking change" because it can cause working code to break. For example:

      if (x is IEnumerable<Animal>)
          ABC();
      else if (x is IEnumerable<Turtle>)
          DEF();
      

      IE 不是协变时,此代码选择 ABC 或 DEF 或两者都不选择.当它是协变时,它不再选择 DEF.

      When IE<T> is not covariant, this code chooses either ABC or DEF or neither. When it is covariant, it never chooses DEF anymore.

      或者:

      class B     { public void M(IEnumerable<Turtle> turtles){} }
      class D : B { public void M(IEnumerable<Animal> animals){} }
      

      之前,如果您在 D 的实例上以海龟序列为参数调用 M,则重载决议选择 B.M,因为这是唯一适用的方法.如果 IE 是协变的,那么重载决议现在选择 DM,因为这两种方法都适用,而且派生程度较高的类上的适用方法总是胜过派生程度较低的类上的适用方法,无论参数类型匹配是否准确.

      Before, if you called M on an instance of D with a sequence of turtles as the argument, overload resolution chooses B.M because that is the only applicable method. If IE is covariant, then overload resolution now chooses D.M because both methods are applicable, and an applicable method on a more-derived class always beats an applicable method on a less-derived class, regardless of whether the argument type match is exact or not.

      或者:

      class Weird : IEnumerable<Turtle>, IEnumerable<Banana> { ... }
      class B 
      { 
          public void M(IEnumerable<Banana> bananas) {}
      }
      class D : B
      {
          public void M(IEnumerable<Animal> animals) {}
          public void M(IEnumerable<Fruit> fruits) {}
      }
      

      如果 IE 是不变的,那么对 d.M(weird) 的调用将解析为 B.M.如果IE突然变协变,那么D.M两种方法都适用,都比基类上的方法好,都不比另一个好,所以,重载解析变得模棱两可,我们报错.

      If IE is invariant then a call to d.M(weird) resolves to B.M. If IE suddenly becomes covariant then both methods D.M are applicable, both are better than the method on the base class, and neither is better than the other, so, overload resolution becomes ambiguous and we report an error.

      当我们决定进行这些重大更改时,我们希望 (1) 这种情况很少发生,以及 (2) 当这种情况出现时,几乎总是因为类的作者试图模拟协方差在没有它的语言中.通过直接添加协方差,希望当代码中断"时在重新编译时,作者可以简单地删除试图模拟现在存在的功能的疯狂齿轮.

      When we decided to make these breaking changes, we were hoping that (1) the situations would be rare, and (2) when situations like this arise, almost always it is because the author of the class is attempting to simulate covariance in a language that doesn't have it. By adding covariance directly, hopefully when the code "breaks" on recompilation, the author can simply remove the crazy gear trying to simulate a feature that now exists.

      这篇关于为什么 IEnumerable&lt;T&gt;在 C# 4 中实现协变?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆