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

本文关键字:ICollection IEnumerable 不应该 为什么 | 更新日期: 2023-09-27 18:22:16

我在寻找问题的解决方案时发表了这篇文章,这让我在那里提出了一个新的答案,并面临以下问题:

考虑到ICollection实现了IEnumerable,并且所有linq扩展都适用于这两个接口,有没有任何场景可以让我从使用IEnumerable而不是ICollection中受益?

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

考虑到所有的ICollection,在任何情况下,都提供了IEnumerable实现的所有功能——既然它本身实现了它——那么我为什么要选择IEnumerable来代替ICollection呢?

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

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

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

我什么时候想要IEnumerable<T>

与其他集合类型和一般语言不同,IEnumerable上的查询是使用延迟求值执行的。这意味着您可能只在枚举中执行几个相关的查询。

值得注意的是,懒惰评估与副作用的交互效果并不好,因为多个枚举可能会产生不同的结果,在使用它时需要记住这一点。在C#这样的语言中,懒惰评估可能是一个非常强大的工具,但如果你不小心,它也会导致无法解释的行为。

我什么时候不想要ICollection<T>

ICollection<T>是一个可变接口,它公开了add和remove方法。除非你想让外部事物改变你对象的内容,否则你不想返回它。同样,出于同样的原因,你通常不想把它作为参数传入。

如果您确实想要集合的显式可变性,请使用ICollection<T>

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

非通用版本

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

我个人倾向于避免非泛型版本,因为在值类型的情况下,装箱/取消装箱缺乏类型安全性和较差的性能。

摘要

如果您想要延迟评估,请使用IEnumerable<T>。对于急切的评估和不变性,使用IReadOnlyCollection<T>。对于显式可变性,请使用ICollection<T>.

IEnumerable为集合提供只读接口,ICollection允许修改。此外,IEnumerable只需要知道如何对元素进行迭代。ICollection必须提供更多信息。

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

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

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

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

正如你所看到的,这些是完全不同的。您不能忽略这些契约的语义,而只关注哪个接口继承了另一个接口。

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