【问题标题】:Why array implements IList?为什么数组实现IList?
【发布时间】:2011-08-23 13:06:59
【问题描述】:

查看System.Array类的定义

public abstract class Array : IList, ...

理论上,我应该能写到这点,开心

int[] list = new int[] {};
IList iList = (IList)list;

我也应该能够从 iList 调用任何方法

 ilist.Add(1); //exception here

我的问题不是为什么会出现异常,而是为什么 Array 实现 IList

【问题讨论】:

  • 问题。我从不喜欢胖接口的想法(这是这种设计的技术术语)。
  • 真的有人关心 LSP 吗?这对我来说似乎很学术。
  • @Gabe,那么您需要使用更大的代码库。实现一个行为(从接口继承)然后简单地忽略你不喜欢/不能支持的东西会导致臭味、混淆、强制转换,最后:错误代码。
  • @Gabe 它的集合意味着可变性而不是它包含的实体。您可以使您的类成员成为实现 IRWList 和 IReadList 的类型,在您的类内部使用 if 作为 IRWList 并将其公开为 IReadList。是的,你必须把复杂性放在某个地方,但我只是不明白这如何适用于无视 LSP 作为一个非常好的设计原则(虽然不知道 IsReadOnly 属性,但从消费者的角度来看这使得 IList 更加复杂)

标签: c# arrays ilist liskov-substitution-principle


【解决方案1】:

因为数组允许通过索引快速访问,而IList/IList<T> 是唯一支持这一点的集合接口。所以也许你真正的问题是“为什么没有索引器的常量集合接口?”对此我没有答案。

集合也没有只读接口。而且我缺少的不仅仅是带有索引器界面的恒定大小。

IMO 应该有更多(通用)集合接口,具体取决于集合的功能。而且名称也应该不同,List 对于带有索引器的东西,IMO 真的很愚蠢。

  • 只是枚举IEnumerable<T>
  • 只读但没有索引器(.Count、.Contains、...)
  • 可调整大小但没有索引器,即设置为 (Add, Remove,...) current ICollection<T>
  • 使用索引器只读(indexer、indexof、...)
  • 带索引器的恒定大小(带设置器的索引器)
  • 带有索引器的可变大小(插入,...)当前IList<T>

我认为当前的集合界面设计不佳。但由于它们有属性告诉你哪些方法是有效的(这是这些方法契约的一部分),它不会违反替换原则。

【讨论】:

  • 感谢您的回答。但我宁愿保留这个问题。原因很简单。接口是一个公共合约。如果一个人实现它,就必须完全实现所有成员,否则它会破坏 LSP 并且通常闻起来很糟糕,不是吗?
  • 它确实破坏了 LSP。如果它没有 list.Add(item) 应该将项目添加到列表中,而不管具体类型如何。例外情况除外。在数组实现中,在非异常情况下抛出异常,这本身就是不好的做法
  • @smelch 很抱歉,您当时弄错了 LSP。数组没有实现add,因此不能替代需要该功能时执行的操作。
  • 我承认它在技术上不违反 LSP只是因为文档声明您应该检查 IsFixedSizeIsReadOnly 属性,它绝对违反了告诉,不要问的原则最不意外的原则。当您只想为 9 种方法中的 4 种抛出异常时,为什么要实现接口?
  • 距离最初的问题已经过去了一段时间。但是现在使用 .Net 4.5,有额外的接口 IReadOnlyListIReadOnlyCollection
【解决方案2】:

documentationIList 的备注部分说:

IList 是 ICollection 接口是基础 所有非通用列表的接口。 IList 实现分为三种 类别:只读、固定大小和 可变大小。一个只读的 IList 无法修改。固定大小的 IList 不允许添加或删除 的元素,但它允许 修改现有元素。一种 可变大小的 IList 允许 添加、删除和修改 元素。

显然,数组属于固定大小的类别,因此根据接口的定义,它是有意义的。

【讨论】:

  • 我猜他们最终会得到很多接口。 IListFixedSize, IListReadOnly...
  • 从文档的角度来看,这实际上是一个很好的答案。但对我来说,它看起来更像是一个黑客。接口必须精简和简单,以便类实现所有成员。
  • @oleksii:我同意。接口和运行时异常并不是最优雅的组合。为了保护Array,它确实显式地实现了Add 方法,从而降低了意外调用它的风险。
  • 直到我们创建一个不允许修改添加/删除的IList的实现。然后文档不再正确。 :P
  • @Magnus - 在 .Net 4.5 中,有额外的接口 IReadOnlyListIReadOnlyCollection
【解决方案3】:

因为并非所有ILists 都是可变的(请参阅IList.IsFixedSizeIList.IsReadOnly),并且数组的行为当然类似于固定大小的列表。

如果你的问题真的是“为什么它实现了一个非泛型接口”,那么答案是这些在泛型出现之前就已经存在了。

【讨论】:

  • @oleksii:不,它不会破坏 LSP,因为接口 IList itself 告诉你它可能不是可变的。如果它实际上被保证是可变的并且数组告诉你否则,那么它将违反规则。
  • 实际上,Array 在泛型IList<T> 的情况下会破坏LSP,在非泛型IList 的情况下不会破坏LSP:enterprisecraftsmanship.com/2014/11/22/…
  • @joe:我不相信有办法,尽管你总是可以尝试投到IList 并检查一下。不过你应该在一个单独的问题中问这个......
【解决方案4】:

这是我们从不清楚如何处理只读集合以及 Array 是否为只读的时代遗留下来的。 IList 接口中有 IsFixedSize 和 IsReadOnly 标志。 IsReadOnly 标志意味着集合根本无法更改,IsFixedSize 意味着集合确实允许修改,但不允许添加或删除项目。

在 .Net 4.5 的时候,很明显需要一些“中间”接口来处理只读集合,因此引入了 IReadOnlyCollection<T>IReadOnlyList<T>

这里有一篇很棒的博文描述了细节:Read only collections in .NET

【讨论】:

    【解决方案5】:

    IList 接口的定义是“表示可以通过索引单独访问的非泛型对象集合。”。数组完全满足这个定义,所以必须实现接口。 调用 Add() 方法时的异常是“System.NotSupportedException:Collection was a fixed size”,并且由于数组无法动态增加其容量而发生。它的容量是在创建数组对象时定义的。

    【讨论】:

      【解决方案6】:

      让数组实现 IList(以及可传递的 ICollection)简化了 Linq2Objects 引擎,因为将 IEnumerable 转换为 IList/ICollection 也适用于数组。

      例如,Count() 最终会在后台调用 Array.Length,因为它被强制转换为 ICollection 并且数组的实现返回 Length。

      如果没有这个,Linq2Objects 引擎将不会对数组进行特殊处理并执行可怕的操作,或者他们需要将代码加倍,为数组添加特殊情况处理(就像他们为 IList 所做的那样)。他们一定选择让数组实现 IList。

      这就是我对“为什么”的看法。

      【讨论】:

        【解决方案7】:

        还有实现细节 LINQ Last 检查 IList ,如果它没有实现 list 他们将需要 2 次检查来减慢所有 Last 调用或让 Last 上的 Array 花费 O(N)

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2010-11-12
          • 1970-01-01
          • 1970-01-01
          • 2011-04-04
          • 1970-01-01
          • 1970-01-01
          • 2016-11-02
          • 1970-01-01
          相关资源
          最近更新 更多