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

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

问题描述

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

 公共IEnumerable接口< T> :IEnumerable的



由于C#4中的定义是:



 公共IEnumerable接口<出T> :IEnumerable的




  • 难道只是为了在LINQ表达式恼人的演员去走?

  • 这会不会引进像同样的问题的String []< ;:对象[] (碎阵方差)在C#?

  • 如何在加入协方差的从一个兼容性问题呢?将前面的代码仍然对.NET更高版本的工作,或者是重新编译必要在这里?关于其他方式?

  • 什么使用这个接口严格不变在所有情况下或者是有可能,某些使用情况会表现出不同呢?

  • 为以前的代码

解决方案

马克的和CodeInChaos的回答都不错,但只是添加一些细节:



首先,这听起来像您有兴趣了解设计过程中,我们经历了,使这个功能。如果是这样的话,我建议你阅读我的长篇系列,我在制订和执行功能写文章。从底部开始:



http://blogs.msdn.com/b/ericlippert/archive/tags/covariance+and+contravariance/default.aspx




难道只是为了在LINQ表达式恼人的演员走?




没有,这不是只是的避免演员LT; T> 表达式,但这样做是鼓励我们做此功能的动机之一。我们意识到,将是数节节攀升为什么我不能用长颈鹿的序列在这个方法,它的动物的序列?的问题,因为LINQ鼓励使用序列类型。我们知道,我们想协加入的IEnumerable< T> 第一个



我们实际上考虑使的IEnumerable< T> 协甚至在C#3,但决定,这将是奇怪不引入任何人使用全功能来做到这一点。




这会不会引进像同样的问题的String []< ;:对象[] (碎阵方差)在C#




它不直接引入问题,因为编译器只允许时,它被称为是类型安全方差。但是,它的保存的破阵列方差问题。随着协方差,的IEnumerable<字符串[]> 隐式转换为的IEnumerable<对象[]> ,因此,如果你有字符串数组的序列,就可以把它看成对象阵列的序列,然后你有同样的问题,因为之前:你可以尝试把一个长颈鹿成字符串数组,并获得运行时异常。


如何在加入协方差的从一个兼容性问题呢?




小心。




将提早代码对.NET更高版本仍然可以工作或需要重新编译吗?




只有一个办法,找出。试试吧,看看有什么失败!



这往往是一个坏主意,试图迫使对.NET点¯x编译代码,如果反对.NETŸ运行X!= Y,无论更改系统类型。




有关其他方式是什么?




同样的答案。




有没有可能是某些使用情况,现在的表现有什么不同?




当然可以。制作一个接口协的地方之前是不变的在技术上是一个重大更改,因为它可能会导致工作代码打破。例如:

 如果(x为IEnumerable的<动物>)
ABC();
,否则如果(x为IEnumerable的<龟>)
DEF();

IE< T> 不是协变这个代码选择不是ABC DEF或两者都不是。当它是协变,它永远不会选择DEF了



或者



  b类{公共无效M(IEnumerable的<龟>龟){}} 
类D:b {公共无效M(IEnumerable的<动物>动物){}}

在,如果你对D与龟作为参数的序列实例称为M,重载选择BM,因为这是唯一可用的方法。如果IE是协变,那么重载解析现在选择DM,因为这两种方法都适用,并在更派生类适用的方法总是胜过一个派生较少类适用的方法,无论参数类型是否匹配是准确还是不。



或者



 类古怪:IEnumerable的<龟>中的IEnumerable< ;香蕉> {...} 
类B
{
公共无效M(IEnumerable的<香蕉>香蕉){}
}
类D:B
{
公共无效M(IEnumerable的<动物>动物){}
公共无效M(IEnumerable的<水果GT;水果){}
}

如果IE是不变再到 DM调用(怪异)解析为BM如果IE浏览器突然变得协则这两种方法DM都适用,无论是不是在基类中的方法更好,也不是比其他的更好,所以,重载决议变得模糊,我们报告一个错误。



当我们决定把这些重大更改,我们希望(1)的情况下,将是罕见的,(2)当这样的情况出现时,几乎总是那是因为类的作者试图在不具有它的语言来模拟协方差。通过直接添加协方差,希望当重新编译代码休息,作者可以直接删除疯狂齿轮试图模拟,现在存在的一个特点。


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

public interface IEnumerable<T> : IEnumerable

Since C# 4 the definition is:

public interface IEnumerable<out T> : IEnumerable

  • 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'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:

http://blogs.msdn.com/b/ericlippert/archive/tags/covariance+and+contravariance/default.aspx

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

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.

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.

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

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?

Carefully.

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!

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.

What about the other way around?

Same answer.

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();

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

Or:

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

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.

Or:

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) {}
}

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.

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天全站免登陆