一个 ICollection<T> 是否有顺序?

33

按照公共API不应返回列表的规则,我正在将所有返回列表的代码盲目转换为返回ICollection<T>

public IList<T> CommaSeparate(String value) {...}

变成

public ICollection<T> CommaSeparate(String value) {...}

虽然 ICollection 有一个 Count,但是无法通过该索引获取项目。

虽然 ICollection 公开了一个枚举器(允许使用 foreach),但我看不到保证枚举的顺序从列表的“顶部”开始,而不是“底部”。

我可以通过避免使用 ICollection,而改用 Collection 来缓解这个问题:

public Collection<T> Commaseparate(String value) {...}

这允许使用Items[index]语法。
不幸的是,我的内部实现构建了一个数组;可以将其强制转换为返回IListICollection,但不能作为Collection
有没有一种方法可以按顺序访问集合中的项?
这引出了更广泛的问题:一个ICollection是否具有顺序?

从概念上来说,想象一下我想解析一个命令行字符串。关键是要保持项目的顺序。

从概念上来说,我需要一个契约,指示一个“有序”的字符串元组集合。在API契约的情况下,为了表示顺序,以下哪种方式是正确的:

IEnumerable<String> Grob(string s)

ICollection<String> Grob(string s)

IList<String> Grob(string s)

Collection<String> Grob(string s)

List<String> Grob(string s)

3
顺便说一下,我认为你误读了你引用进行重构的原始文章。它说不要传递List<T>(这个类)的实例,而不是IList<T>(这个接口)。如果接口需要索引访问,则在我看来完全可以传递IList<T> - Chris Shain
3
LINQ的OrderBy返回的是IOrderedEnumerable,这个怎么处理? - Euphoric
9个回答

23

ICollection<T>接口并没有规定元素的顺序。元素将按照返回的对象中指定的顺序排列。例如,如果返回一个SortedDictionaryValues集合,则元素将按照字典比较器定义的顺序排列。

如果你需要方法根据契约返回一个具有特定顺序要求的类型,则应在方法签名中表示,以返回更具体的类型。

无论返回的对象运行时类型如何,都要考虑当静态引用为IList<T>ICollection<T>时的行为:当调用GetEnumerator()(可能是隐式地在foreach循环中)时,将调用相同的方法并获取相同的对象,无论引用的静态类型如何。因此,无论CommaSeparate()方法的返回类型是什么,它的行为都将是相同的。

额外的想法:

正如其他人指出的那样,FXCop规则警告不要使用List<T>,而不是IList<T>;你链接到的问题是在问为什么FXCop不推荐使用List<T>代替IList<T>,这是另一件事。如果我想象你正在解析一个命令行字符串,其中顺序很重要,那么我建议你继续使用IList<T>


1
我实际上并没有返回一个数组。我只是在我的实现中使用了一个数组;但这是一个实现细节,我希望隐藏它,并允许将来更改。 - Ian Boyd
@IanBoyd 已经明白了。最后一段关于数组的评论将适用于无论从方法返回什么类型。我会编辑以澄清。 - phoog
1
太好了,那就用 IList 吧! - Ian Boyd

12

ICollection没有保证顺序,但实现它的类可能(也可能不)有顺序。

如果你想返回一个有序集合,那么返回一个IList<T>,并且不要过分关注FxCop通常很好但非常泛泛的建议。


4
ICollection实例的顺序取决于实现它的类。也就是说,把List<T>作为ICollection引用并不会改变它的顺序。

同样地,如果你把无序集合作为ICollection访问,那么它也不会对无序集合施加任何顺序。

所以,针对你的问题:

ICollection是否具有顺序?

答案是:完全取决于实现它的类。

4
不,ICollection不意味着有序。

3

ICollection是一个接口,没有实现或明确的排序规范。这意味着如果您返回按顺序枚举的内容,任何使用您的ICollection的内容都将按顺序枚举。

排序仅由底层实现对象暗示。 ICollection中没有规定它应该有序还是无序。多次枚举结果会调用底层对象的枚举器,这是那些规则唯一设置的地方。一个对象并不会因为继承了这个接口而改变枚举方式。如果接口指定它是一个有序结果,那么你可以安全地依赖于实现对象的有序结果。


2
ICollection只是一个接口,但它也是行为规则的声明。 - Ian Boyd
1
当然可以,但接口中没有任何内容表明它应该被排序或以其他方式呈现。 - Jim D'Angelo

3
一个 ICollection<T> 只是一个接口;它是否有序完全取决于其底层实现(应该是不透明的)。
如果您想按索引访问它,您需要将其作为 IList<T> 返回,它既是 IEnumerable<T> 也是 ICollection<T>。但是,需要记住的是,取决于底层实现,获取集合中任意项可能需要平均 O(N/2) 的时间。
我的倾向是完全避免使用“集合”接口,而是使用自定义类型来表示集合,并公开适合该类型的适当逻辑操作。

3

ICollection<T> 可能有顺序,但实际排序取决于实现它的类。 它没有给定索引处项目的访问器。 IList<T> 专门为提供按索引访问的接口。


如果我两次迭代 ICollection,那么两次获取的项目顺序会相同吗? - Ian Boyd
3
这取决于实现它的类。不能保证。 - Krizz

1

这取决于实例的实现。假设是List的ICollection具有顺序,而假设是Collection的ICollection则没有。

所有ICollections都实现IEnumerable,它以有序或无序方式逐个返回项目。

编辑:针对您在问题中提供的关于命令行解析的其他示例,我认为适当的返回类型取决于之后对这些参数执行的操作,但IEnumerable可能是正确的选择。

我的理由是IList、ICollection及其具体实现允许修改从Grob返回的列表,这可能不是您想要的。由于.NET没有索引序列接口,因此IEnumerable是防止调用者尝试修改他们返回的参数列表等怪异操作的最佳选择。


-1
如果你预计你的方法在当前和将来的所有版本中都没有难度返回一个能够快速轻松检索第N项的对象,请使用类型IList<T>返回一个引用,该引用实现了IList<T>和非泛型ICollection接口。如果你预计某些当前或将来的版本可能无法快速轻松地返回第N项,但是可以立即报告项目数量,请使用类型ICollection<T>返回一个引用,该引用实现了ICollection<T>和非泛型ICollection接口。如果你预计当前或将来的版本可能甚至不知道有多少项,则返回IEnumerable<T>。排序的问题是无关紧要的;访问第N个元素的能力意味着存在定义好的序列,但是ICollection<T>IEnumerable<T>一样既不多也不少地描述了排序。

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