在.NET中,引用类型数组是共变体。这被认为是一个错误。但是,我不明白为什么这么糟糕,请考虑以下代码:
string[] strings = new []{"Hey there"};
object[] objects = strings;
objects[0] = new object();
哦,好吧,这会编译并在运行时失败。当我们试图将一个对象粘贴到一个字符串[]中时。好的,我同意臭,但是T []扩展了Array并且还实现了IList
(和IList<T>
,我想知道它是否实现了IList<BaseType>
...&gt;。数组和IList让我们犯同样可怕的错误。
string[] strings = new []{"Hey there"};
Array objects = strings;
objects.SetValue(new object(),new[]{0});
IList版本
string[] strings = new []{"Hey there"};
IList objects = strings;
objects[0] = new object();
T []类是由CLR生成的,必须包含对set_Item
方法等效的类型检查(数组实际上没有)。
设置为T []的问题是否必须在运行时进行类型检查(这违反了编译时期望的类型安全性)?当有相同的方法通过上面提供的手段射击自己的脚时,为什么它被认为有害于阵列显示这种属性?
答案 0 :(得分:23)
在.NET中,引用类型数组是共变体。这被认为是一个错误。
某些人认为类型安全破坏数组协方差是.NET设计中的一个错误。所有人都不这么认为。我不认为这是错误;我认为这是一个不幸的选择。所有设计过程都涉及不合需要的选择。在这种情况下,选择是添加一个不安全的隐式转换,它会对所有数组写入产生运行时成本,或者构建一个无法轻松实现Java类型系统的类型系统。这是一个艰难的选择,类型系统的设计者用他们拥有的信息做出了最好的选择。
这种解释当然只是一个问题;那不就是Java的设计师犯了错误的情况吗?可能是的,可能没有;可能Java的设计者也在他们的类型系统的设计中面临权衡。关于Java类型系统开发历史的任何专家谁愿意在这里进行权衡,我有兴趣知道。
我,凭借十年后见之明的好处,如果.NET类型系统的设计者选择避开安全破坏阵列协方差,我个人会更喜欢它。但这并不能使这种选择成为“错误”,只是让它有点不幸。
设置为T []的问题是否必须在运行时进行类型检查(这违反了编译时期望的类型安全性)?
是。这意味着看起来应该总是成功运行的代码在运行时可能会失败。这意味着正确的代码会对其施加性能损失。
为什么当有相同的方法通过上面提供的手段射击自己时,对阵列表现出这种属性是否有害?
这是一个奇怪的问题。问题基本上是“我已经有两把枪,我可以用脚射击自己了,所以为什么用三分之一将自己射到脚上对我有害?”
存在两种违反类型安全的危险模式并不会使第三种模式的危险性降低。
当你绝对肯定地知道你正在做的事情是安全的时候,那些违反类型安全的语言和运行时功能存在,即使编译器不知道它。如果您不能很好地理解这些功能以便安全使用它们,请不要使用它们。
答案 1 :(得分:13)
是的,IList
和Array
允许您犯同样的错误 - 因为它们是开头的弱类型API。
数组看起来就像它们是强类型的(在编译时),但实际上它们不是。他们可以这么容易安全(而且速度更快),但事实并非如此。这只是性能和编译时安全性的浪费机会:(
答案 2 :(得分:1)
我认为你关于IList
的说明指出了值得考虑的事情。
数组实现IList
非常有用。实现它的其他集合也很有用。
现在,在过去的5年中,我们经常发现处理IList<T>
更有用(或同样有用且更安全)。
在.NET2.0之前,我们没有IList<T>
,我们只有IList
。很多情况下,人们可能会在数组和其他集合之间移动,其中泛型(在最好的情况下)在泛型之前,在许多情况下现在让我们更有信心地在类型集合和类型数组之间移动。
因此,在做出相关决策时,支持协变数组的论据比现在更大。而且,当它没有泛型时,他们在Java中建立类似的决策只会增加这一事实。
答案 3 :(得分:0)
阵列差异的一个代价是对非密封参考类型的数组的赋值要贵一些。但考虑到参考类型的分配已经与GC有很大关系,我想这个成本并不重要。