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