为什么数组实现了IList接口?

162

查看System.Array类的定义。

public abstract class Array : IList, ...

从理论上讲,我应该能够编写这一部分并感到满意

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

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

 ilist.Add(1); //exception here

我的问题不是为什么我会得到一个异常,而是为什么数组实现了IList接口


27
好问题。我从来不喜欢臃肿的接口设计(这是此类设计的技术术语)。 - Konrad Rudolph
4
@Henk,请参见:http://blogs.msdn.com/b/bclteam/archive/2004/11/19/267089.aspx - Anthony Pegram
2
有人真的关心LSP吗?对我来说,它似乎相当学术化。 - Gabe
15
@Gabe,那么你需要处理更大的代码库。实现一个行为(继承接口),然后简单地忽略你不喜欢/无法支持的东西将导致臭味、混淆、强制转换最终:有缺陷的代码。 - Marius
4
@Gabe,这是指集合本身可以被改变而不是其包含的实体。您可以将自己的类成员设置为实现IRWList<>和IReadList<>的类型,内部使用它作为IRWList<>,并将其公开为IReadList。是的,您必须在某个地方放置复杂性,但我只是不明白这如何涉及到忽略LSP作为一个非常好的设计原则(虽然不知道IsReadOnly属性,这使得IList从消费者的角度更加复杂)。 - Marius
显示剩余3条评论
8个回答

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

同时,也没有适用于集合的只读接口。我比缺少常量大小索引器接口更加需要这些只读接口。

在我看来,应该根据集合特性添加多个(通用)集合接口。这些接口的名称也应该不同,例如,使用索引器的列表称为List实在太愚蠢了。

  • 仅枚举 IEnumerable<T>
  • 只读但没有索引器(.Count,.Contains等)
  • 可调整大小但没有索引器,例如 (Add,Remove等) 当前ICollection<T>
  • 只读带索引器(索引器,indexof等)
  • 大小恒定并带有索引器(具有setter的索引器)
  • 带索引器的变量大小(Insert等)当前IList<T>

我认为当前的集合接口设计很糟糕。但由于它们具有告诉您哪些方法是有效的属性(这是这些方法契约的一部分),因此它不会破坏代换原则。


15
谢谢你的回答。但我宁愿保留原来的问题。原因很简单,接口是一个公共契约。如果有人实现了它,就必须完全实现所有成员,否则就会违反LSP,一般会出现问题,不是吗? - oleksii
18
它确实破坏了LSP。如果它没有,list.Add(item)应该无论具体类型如何都会将item添加到列表中。除非是特殊情况。在数组实现中,它在非特殊情况下抛出异常,这本身就是一个不好的做法。 - Rune FS
2
@smelch 很抱歉,您对LSP理解有误。数组不实现add,因此在需要此功能时不能替换为具有此能力的内容。 - Rune FS
9
我承认从技术上讲它没有违反LSP,仅因为文档说明您应该检查IsFixedSizeIsReadOnly属性,但它明显违反了“告诉,不要问原则”和“最小惊讶原则”。当您打算对9个方法中的4个抛出异常时,为什么要实现接口呢? - Matthew
18
原问题已经有一段时间了,但是现在在 .Net 4.5 中增加了额外的接口 IReadOnlyListIReadOnlyCollection - Tobias
显示剩余4条评论

46
IList的文档中的备注部分说明:

IList 是ICollection接口的后代,并且是所有非泛型列表的基本接口。 IList 实现可以分为三种类别:只读、固定大小和可变大小。 只读 IList 不能被修改。 固定大小的 IList 不允许添加或删除元素,但允许修改现有元素。 可变大小的 IList 允许添加、删除和修改元素。

很明显,数组属于固定大小的范畴,因此根据该接口的定义是有意义的。


5
我猜他们最终可能会得到很多接口,如IListFixedSize(固定大小列表接口)、IListReadOnly(只读列表接口)等。 - Magnus
14
从文档的角度来看,那其实是一个不错的答案。但对我来说,它更像一个hack。接口必须是薄而简单的,这样类才能实现所有成员。 - oleksii
1
@oleksii: 我同意。接口和运行时异常并不是最优雅的组合。为了捍卫 Array,它确实显式实现了 Add 方法,这降低了意外调用它的风险。 - Brian Rasmussen
直到我们创建了一个禁止修改和添加/删除的IList实现,那么文档就不再正确了。 :P - Timo
2
@Magnus - 在 .Net 4.5 中,有额外的接口 IReadOnlyListIReadOnlyCollection - RBT
显示剩余2条评论

19

因为并非所有的IList都是可变的(请参见IList.IsFixedSizeIList.IsReadOnly),而数组确实像固定大小的列表一样运作。

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


10
不会违反LSP,因为接口IList本身就告诉你它可能不可变。如果它确实保证是可变的,而数组告诉你它不是,那么才会违反规则。 - user541686
2
实际上,在泛型 IList<T> 的情况下,数组确实会破坏 LSP,而在非泛型 IList 的情况下不会破坏它:http://enterprisecraftsmanship.com/2014/11/22/read-only-collections-and-lsp/ - Vladimir
@joe: 我不认为有一种方法,但你可以尝试将其转换为IList并检查。不过你应该在一个单独的问题中问这个。 - user541686

6
这是我们从处理只读集合并不清楚Array是否为只读的时代遗留下来的。IList接口中有IsFixedSize和IsReadOnly标志。IsReadOnly标志表示集合根本无法更改,而IsFixedSize表示集合允许修改,但不允许添加或删除项。
在.Net 4.5时,明确需要一些“中间”接口来处理只读集合,因此引入了IReadOnlyCollection和IReadOnlyList。
这里有一篇很好的博客文章描述了细节:.NET中的只读集合

数组不是只读的,您可以在索引处设置值。数组只有固定的大小,但没有“IFixedSizeList”。 - Wouter

0

此外,LINQ 的实现细节为 IList 进行了最后的检查,如果它没有实现 list,则需要进行两个检查来减慢所有 Last 调用,或者在数组上使用 O(N) 的 Last。


0

IList接口的定义是“表示一组非泛型对象,可以通过索引单独访问”。数组完全满足这个定义,因此必须实现该接口。 调用Add()方法时出现异常:“System.NotSupportedException:集合大小固定”,这是因为数组无法动态增加其容量。其容量在创建数组对象时被定义。


0

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

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

如果没有这个,Linq2Objects引擎将不会对数组进行特殊处理,并表现得非常糟糕,或者他们需要加倍的代码来添加数组的特殊情况处理(就像他们为IList所做的那样)。他们必须选择使数组实现IList。

这是我的看法。


0

Array 只是 IList 的许多可能实现之一。

由于代码应该松散耦合,依赖于抽象等等... 使用连续内存(数组)来存储其值的 IList 的具体实现称为 Array。我们不会将 IList 添加到 Array 类中,这只是错误的推理顺序;Array 作为数组实现了 IList

异常正是接口定义的内容。如果您知道整个接口而不仅仅是单个方法,则不会感到惊讶。接口还提供了检查 IsFixedSize 属性并查看是否可以安全调用 Add 方法的机会。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接