为什么我不应该总是使用ICollection而不是IEnumerable
本文关键字:ICollection IEnumerable 不应该 为什么 | 更新日期: 2023-09-27 18:22:16
我在寻找问题的解决方案时发表了这篇文章,这让我在那里提出了一个新的答案,并面临以下问题:
考虑到ICollection
实现了IEnumerable
,并且所有linq扩展都适用于这两个接口,有没有任何场景可以让我从使用IEnumerable
而不是ICollection
中受益?
例如,非通用IEnumerable
不提供Count
扩展。两个ICollection
接口都可以。
考虑到所有的ICollection
,在任何情况下,都提供了IEnumerable
实现的所有功能——既然它本身实现了它——那么我为什么要选择IEnumerable
来代替ICollection
呢?
与以前ICollection
不可用的框架的向后兼容性?
我认为这里实际上有两个问题需要回答。
我什么时候想要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
。