为什么我不应该总是使用 ICollection 而不是 IEnumerable?

Why I should not always be using ICollection instead of IEnumerable?

我在 this post 寻找我的问题的解决方案时结束了 - 这导致我在那里提出了一个新的答案 - 并且 - 面临以下问题:

考虑到 ICollection 实现了 IEnumerable,并且所有 linq 扩展都适用于这两个接口,是否有任何场景我会从使用 IEnumerable 而不是 [=10] 中获益=] ?

例如,非通用 IEnumerable 不提供 Count 扩展。 ICollection 接口都可以。

给定所有 ICollection,无论如何,提供所有功能 IEnumerable 实现 - 因为它本身实现了它 - 为什么我会选择 IEnumerable 代替 [=10] =] ?

向后兼容以前 ICollection 不可用的框架?

IEnumerable 为集合提供只读接口,ICollection 允许修改。 IEnumerable 也只需要知道如何遍历元素。 ICollection 必须提供更多信息。

这在语义上是不同的。您并不总是希望提供修改集合的功能。

有一个 IReadOnlyCollection 但它没有实现 ICollection。这是 C# 的设计,ReadOnly 是一个不同的精简接口。

Tim的观点很重要。 Count 的内部工作可能有很大不同。 IEnumerable 不需要知道它跨越了多少个元素。集合有一个 属性,所以它必须知道它包含多少个元素。这是另一个重要的区别。

想法是使用满足要求的最简单的契约(接口):ICollection是一个集合,IEnumerable是一个序列。一个序列可以延迟执行,它可以是无限的,等等。接口 IEnumerable 只是告诉你可以枚举序列,仅此而已。这与 ICollection 不同,后者表示包含有限数量项目的实际集合。

如您所见,它们完全不同。你不能忽视这些契约的语义,而只关注哪个接口继承了另一个。

如果您的算法只涉及输入数据的枚举,那么它应该采用IEnumerable。如果您根据合同处理集合(即,您期望集合而不是其他任何东西),那么您应该使用 ICollection.

我认为这里实际上有两个问题需要回答。

我什么时候要IEnumerable<T>

与其他集合类型和一般语言不同,对 IEnumerable 的查询是使用惰性求值执行的。这意味着您可以仅在枚举中执行多个相关查询。

值得注意的是惰性求值并不能很好地与副作用交互,因为多次枚举可能会给出不同的结果,您在使用它时需要牢记这一点。在像 C# 这样的语言中,惰性计算可以是一个非常强大的工具,但如果您不小心,也会成为无法解释的行为的来源。

我什么时候不想ICollection<T>

ICollection<T> 是一个可变接口,除其他外,它公开了添加和删除方法。除非您希望外部事物改变对象的内容,否则您不希望返回它。同样,出于同样的原因,您通常不希望将其作为参数传递。

如果你确实想要集合的显式可变性,一定要使用 ICollection<T>.

此外,与 IEnumerable<T>IReadOnlyCollection<T> 不同,ICollection<T> 不是协变的,这会降低某些用例中类型的灵活性。

非通用版本

当涉及到这些接口的非通用版本时,情况发生了一些变化。在这种情况下,两者之间唯一真正的区别是 IEnumerable 提供的懒惰求值和 ICollection 提供的急切求值。

我个人倾向于避免使用非泛型版本,因为在值类型的情况下 boxing/unboxing 缺乏类型安全性和性能不佳。

总结

如果你想惰性求值,使用IEnumerable<T>。对于急切的评估和不变性,请使用 IReadOnlyCollection<T>。对于显式可变性,使用 ICollection<T>.