为啥数组实现IList?
Posted
技术标签:
【中文标题】为啥数组实现IList?【英文标题】: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?
【问题讨论】:
好问题。我从不喜欢胖接口的想法(这是这种设计的技术术语)。 @Henk,见:blogs.msdn.com/b/bclteam/archive/2004/11/19/267089.aspx 真的有人关心 LSP 吗?这对我来说似乎很学术。 @Gabe,那么您需要使用更大的代码库。实现一个行为(从接口继承)然后简单地忽略你不喜欢/不能支持的东西会导致臭味、混淆、强制转换,最后:错误代码。 @Gabe 它的集合意味着可变性而不是它包含的实体。您可以使您的类成员成为实现 IRWList 和 IReadList 的类型,在您的类内部使用 if 作为 IRWList 并将其公开为 IReadList。是的,你必须把复杂性放在某个地方,但我只是不明白这如何适用于无视 LSP 作为一个非常好的设计原则(虽然不知道 IsReadOnly 属性,但从消费者的角度来看这使得 IList 更加复杂) 【参考方案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只是因为文档声明您应该检查 IsFixedSize
和 IsReadOnly
属性,它绝对违反了告诉,不要问的原则和最不意外的原则。当您只想为 9 种方法中的 4 种抛出异常时,为什么要实现接口?
距离最初的问题已经过去了一段时间。但是现在使用 .Net 4.5,有额外的接口 IReadOnlyList 和 IReadOnlyCollection。【参考方案2】:
documentationIList
的备注部分说:
IList 是 ICollection 接口是基础 所有非通用列表的接口。 IList 实现分为三种 类别:只读、固定大小和 可变大小。一个只读的 IList 无法修改。固定大小的 IList 不允许添加或删除 的元素,但它允许 修改现有元素。一种 可变大小的 IList 允许 添加、删除和修改 元素。
显然,数组属于固定大小的类别,因此根据接口的定义,它是有意义的。
【讨论】:
我猜他们最终会得到很多接口。 IListFixedSize, IListReadOnly... 从文档的角度来看,这实际上是一个很好的答案。但对我来说,它看起来更像是一个黑客。接口必须精简和简单,以便类实现所有成员。 @oleksii:我同意。接口和运行时异常并不是最优雅的组合。为了保护Array
,它确实显式地实现了Add
方法,从而降低了意外调用它的风险。
直到我们创建一个不允许修改和添加/删除的IList
的实现。然后文档不再正确。 :P
@Magnus - 在 .Net 4.5 中,有额外的接口 IReadOnlyList 和 IReadOnlyCollection。【参考方案3】:
因为并非所有IList
s 都是可变的(请参阅IList.IsFixedSize
和IList.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)
【讨论】:
以上是关于为啥数组实现IList?的主要内容,如果未能解决你的问题,请参考以下文章
数组如何实现 IList<T> 而无需在 C# 中实现属性“Count”? [复制]
为啥是List list = new ArrayList,而不直接用ArrayList