为什么IEnumerable <T>在C#4中是协变的?
在早期版本的C# IEnumerable
中定义如下:
public interface IEnumerable<T> : IEnumerable
由于C#4的定义是:
public interface IEnumerable<out T> : IEnumerable
- 只是为了让LINQexpression式中的恼人的表演消失吗?
- 这不会像C#中的
string[] <: object[]
(破坏数组方差)一样引入相同的问题吗? - 从兼容性的angular度来看,如何增加协变? 早期的代码仍然可以在更高版本的.NET上工作,或者在这里重新编译吗? 那反过来呢?
- 之前的代码是否使用这个接口在所有情况下都是严格不变的,或者现在某些用例可能会有所不同?
Marc和CodeInChaos的答案相当不错,但只是增加了一些细节:
首先,这听起来像您有兴趣了解我们devise过程中所做的function。 如果是这样,那么我鼓励你阅读我在devise和实现这个function时写的冗长的一系列文章。 从底部开始:
http://blogs.msdn.com/b/ericlippert/archive/tags/covariance+and+contravariance/default.aspx
只是为了让LINQexpression式中的恼人的表演消失吗?
不,这不仅仅是为了避免Cast<T>
expression,而是鼓励我们做这个function的动机之一。 我们意识到,为什么我不能用这个方法中的一系列动物来使用一系列长颈鹿呢? 问题,因为LINQ鼓励使用序列types。 我们知道我们想要首先向IEnumerable<T>
添加协方差。
我们实际上已经考虑过甚至在C#3 IEnumerable<T>
协变,但是决定如果不引入任何人使用的全部特性,这将是奇怪的。
这不会像C#中的
string[] <: object[]
(破坏数组方差)一样引入相同的问题吗?
它不直接引入这个问题,因为编译器只有在已知types安全的情况下才允许有差异。 但是,它确实保留了破坏的数组方差问题。 使用协方差, IEnumerable<string[]>
可隐式转换为IEnumerable<object[]>
,因此如果有一串string数组,则可以将其视为一系列对象数组,然后就像以前一样:你可以尝试把一个长颈鹿放到这个string数组中,并在运行时得到一个exception。
从兼容性的angular度来看,如何增加协变?
小心。
早期的代码仍然可以在更高版本的.NET上工作,或者在这里重新编译吗?
只有一个办法找出来。 尝试一下,看看有什么失败!
如果X!= Y,试图强制针对.NET X编译的代码针对.NET Y运行,而不考虑types系统的更改通常是一个糟糕的主意。
那反过来呢?
同样的答案。
某些用例现在可能会有所不同吗?
绝对。 在以前不变的界面协变下,在技术上是一个“突变”,因为它可能导致工作代码被破坏。 例如:
if (x is IEnumerable<Animal>) ABC(); else if (x is IEnumerable<Turtle>) DEF();
当IE<T>
不是协变时,这个代码selectABC或DEF或者不select。 当它是协变时,它不再selectDEF。
要么:
class B { public void M(IEnumerable<Turtle> turtles){} } class D : B { public void M(IEnumerable<Animal> animals){} }
之前,如果您以D序列的海龟作为参数对M的实例进行调用,则重载决策selectBM,因为这是唯一适用的方法。 如果IE是协变的,那么重载parsing现在selectDM,因为这两种方法都是适用的,并且对于更派生类的适用方法总是在较less派生类上击败适用的方法,而不pipe参数types匹配是否准确。
要么:
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是不变的,那么调用dM(weird)
parsing为BM。如果IE突然变成协变,那么两种方法DM都适用,都比基类上的方法好,而且都不比其他更好,所以,重载parsing变得模棱两可,我们报告一个错误。
当我们决定做出这些突破性的改变时,我们希望(1)情况是罕见的,(2)当这种情况出现时,几乎总是因为类的作者试图模仿语言的协变没有它。 通过直接添加协方差,希望当代码在重新编译时“中断”时,作者可以简单地删除试图模拟现在存在的特征的疯狂装置。
为了:
只是为了让LINQexpression式中的恼人的表演消失吗?
它使事情像人们普遍期望的那样行事;
这不会像C#中的
string[] <: object[]
(破坏数组方差)一样引入相同的问题吗?
没有; 因为它不公开任何Add
机制或类似的(而且不能;在编译器中强制执行)
从兼容性的angular度来看,如何增加协变?
CLI已经支持它,这只会让C#(以及一些现有的BCL方法)意识到它
早期的代码仍然可以在更高版本的.NET上工作,或者在这里重新编译吗?
但它完全向后兼容: 依赖于C#4.0差异的C#不会在C#2.0等编译器中编译
那反过来呢?
这不是不合理的
之前的代码是否使用这个接口在所有情况下都是严格不变的,或者现在某些用例可能会有所不同?
一些BCL调用( IsAssignableFrom
)现在可能会有所不同
只是为了让LINQexpression式中的恼人的表演消失吗?
不仅在使用LINQ时。 在有IEnumerable<Derived>
任何地方都是有用的,代码需要一个IEnumerable<Base>
。
这不会像C#中的
string[]
<:object[]
(破坏数组方差)一样引入相同的问题吗?
不,因为只有在返回该types值的接口上才允许协方差,但不接受它们。 所以这是安全的。
从兼容性的angular度来看,如何增加协变? 早期的代码仍然可以在更高版本的.NET上工作,或者在这里重新编译吗? 那反过来呢?
我认为已编译的代码将主要按原样工作。 一些运行时types检查( IsAssignableFrom
,…)将在之前返回false的情况下返回true。
之前的代码是否使用这个接口在所有情况下都是严格不变的,或者现在某些用例可能会有所不同?
不知道你是什么意思
最大的问题是与超载分辨率有关。 由于现在可以进行额外的隐式转换,因此可以select不同的过载。
void DoSomething(IEnumerabe<Base> bla); void DoSomething(object blub); IEnumerable<Derived> values = ...; DoSomething(values);
但是,当然,如果这些超载的行为不同,API已经devise得不好。