【问题标题】:Why is array co-variance considered so horrible?为什么数组协方差被认为如此可怕?
【发布时间】:2010-11-30 19:05:57
【问题描述】:

在 .NET 中,引用类型数组是协变的。这被认为是一个错误。但是,我不明白为什么这很糟糕,请考虑以下代码:

string[] strings = new []{"Hey there"};
object[] objects = strings;
objects[0] = new object();

哦,这会编译并在运行时失败。当我们试图将一个对象粘贴到一个字符串 [] 中时。好吧,我同意这很臭,但是 T[] 扩展了 Array 并且还实现了IList(和IList<T>,我想知道它是否实现了IList<BaseType>...>。Array 和 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[] 必须在运行时进行类型检查(这违反了您在编译时期望的类型安全)?当有等效的方法通过上面提供的方法射中自己的脚时,为什么认为阵列表现出此属性是有害的?

【问题讨论】:

  • 你的意思是objects[0] = new object(); ?

标签: c# arrays


【解决方案1】:

在 .NET 中,引用类型数组是协变的。这被认为是一个错误。

有些人认为类型安全破坏数组协方差是 .NET 设计中的一个错误。并不是所有人都这么认为。我不认为这是一个错误;我认为这是一个不幸的选择。所有设计过程都涉及在不受欢迎的替代方案之间进行选择。在这种情况下,选择是添加一个不安全的隐式转换,这会对所有数组写入产生运行时成本,或者构建一个无法轻松实现 Java 类型系统的类型系统。这是一个艰难的选择,类型系统的设计者根据他们所拥有的信息做出了最佳选择。

这种解释当然只是在乞求问题;那不就是Java的设计者犯了一个错误吗?可能是,可能不是; Java 的设计者很可能在其类型系统的设计中也面临权衡取舍。任何想要了解这些权衡是什么的 Java 类型系统开发历史的专家,我都很想知道。

如果 .NET 类型系统的设计者选择避开破坏安全的数组协变,我个人会更喜欢十年后的事后诸葛亮。但这并不意味着这个选择是一个“错误”,它只是让它有点不幸。

是否担心设置为 T[] 必须在运行时进行类型检查(这违反了您在编译时期望的类型安全)?

是的。这意味着看起来应该始终成功运行的代码可能会在运行时失败。这意味着正确的代码会对它施加性能损失。

当有等效的方法通过上面提供的方法射中自己的脚时,为什么认为数组表现出此属性是有害的?

这是一个奇怪的问题。问题本质上是“我已经有两支枪可以射中自己的脚,那么为什么认为用第三支枪射中自己的脚对我有害呢?”

存在两种违反类型安全的危险模式并不会降低第三种这样的模式的危险性。

语言和运行时特性违反了类型安全,当你完全肯定地知道你正在做的事情是安全的,即使编译器不知道它。如果您对这些功能的理解不足以安全地使用它们,请不要使用它们。

【讨论】:

  • 我希望他们能接受您的洞察,因为您似乎在做出决定之前就预见到了许多此类问题。会更有意义。但我想他们通常会同意我猜大多数人的意见,对吧?
  • @Joan:我从来没有预见到这一切。我说的是十年后见之明。
  • 一如既往,您的洞察力非常有帮助。我正在尝试构建一个可能以后期绑定方式使用的 API,因此我试图决定是否应该公开类似 object[] 的东西,这可能真的是 string[] 或将其公开为 @987654323 @ .我希望最终用户可以更改数组内容。我想更好的问题是我应该使用数组协方差还是不应该,因为它可能会给最终用户留下错误的印象。
  • 谢谢埃里克,是的,我以为您考虑过这种情况,但决定却相反。我明白你的意思了。但你知道他们说什么:“后见之明总比远见好”:O
【解决方案2】:

是的,IListArray 允许您犯同样的错误 - 因为它们从一开始就是弱类型 API。

数组看起来就像它们是强类型的(在编译时),但实际上它们不是。他们可以如此轻松地安全(并且更快),但事实并非如此。这只是浪费了性能和编译时安全的机会:(

【讨论】:

  • 抖动不能优化出类型检查吗?
  • @Michael B:它可能在一些情况下可以,但不是全部。 (例如,如果它是密封类型,那么我认为跳过检查应该没问题。)老实说,更让我感到困扰的是编译时类型安全的丢失机会。看起来类型安全但并不烦人的 API :(
  • 我同意我更大的问题不是性能,因为数组往往非常快,而编译时间检查是更大的问题。实际上,真正可怕的是string[] 同时实现了IList<string>IList<object>,这意味着我们得到了这种可怕的协方差形式。
【解决方案3】:

我认为您关于 IList 的注释指出了一些值得考虑的地方。

IList 由数组实现非常有用。还有其他实现它的集合也很有用。

现在,事实上在过去的 5 年里,我们经常发现处理 IList<T> 更有用(或同样有用和更安全)。

在 .NET2.0 之前,我们没有 IList<T>,我们只有 IList。很多情况下,在泛型之前,我们可能会在数组和其他集合之间移动(充其量),这在许多情况下让我们更加自信地在类型化集合和类型化数组之间移动。

因此,在做出相关决定时,支持协变数组的论据比现在更大。并且当 Java 没有泛型时,它们建立在类似的决策之上,这只会增加这一事实。

【讨论】:

    【解决方案4】:

    array-variance 的一个代价是对非密封引用类型的数组的赋值要贵一些。但考虑到对引用类型的分配已经与 GC 有很大关系,我想成本并不重要。

    【讨论】:

      猜你喜欢
      • 2015-03-02
      • 1970-01-01
      • 2014-01-09
      • 1970-01-01
      • 2014-12-14
      • 2020-12-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多