【问题标题】:Why I should not always be using ICollection instead of IEnumerable?为什么我不应该总是使用 ICollection 而不是 IEnumerable?
【发布时间】:2016-02-18 16:49:44
【问题描述】:

我在寻找我的问题的解决方案时最终进入了this 帖子 - 这导致我在那里提出了一个新的答案 - 并且 - 面临以下问题:

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

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

鉴于所有ICollection,无论如何,提供IEnumerable 实现的所有功能——因为它本身实现了它——那我为什么要选择IEnumerable 来代替ICollection

向后兼容以前没有ICollection 的框架?

【问题讨论】:

  • IEnumerableICollection 的父接口。所以如果你的方法只接受ICollection<T>,它就不像接受IEnumerable<T>那样可重用。另请注意,许多 LINQ 扩展方法尝试强制转换为 IListICollection 以使用属性而不是枚举序列。 Enumerable.Count 已优化,如您所见 here
  • @TimSchmelter:谢谢蒂姆。声明 因此,如果您说您的方法仅采用 ICollection 它不像您采用 IEnumerable 那样可重用但是确实回答“为什么不使用 A 而不是 B,如果A 是 B+”。如果在任何情况下 IEnumerable 被更广泛地使用 - 这仍然不是不使用 ICollection 的原因,因为 ICollection 是一个 IEnumerable(除非上述内容是错误的)。
  • @Veverke 除非有特殊原因不这样做,否则您应该始终更喜欢层次结构中具有您需要的更高级别。这提供了更大的灵活性:如果 ICollection 中没有您需要的特定内容,则使用 IEnumerable 可以提供更大的灵活性。
  • @Jcl:我同意,这是一个强项。然而,正如我对蒂姆上述声明之一的评论一样——我仍然认为这本身并没有回答这个问题。另一方面,下面 Luk 的回答带来了 IEnumerable 优于 ICollection 的功能。 IEnumerable 可能在任何地方都更频繁 - 但如果我不关心可移植性,那么它甚至不会触及问题的重点。
  • @Veverke IEnumerable 没有ICollection 的任何功能。因为它是一个更简单的合约,如果有的话,它有 less 的特性:ICollection 拥有 IEnumerable 所拥有的一切(它实现了 IEnumerable),然后还有更多的东西。另外,我真的不明白可移植性与这一切有什么关系。我认为您要么在这里混合了一些东西,要么我根本不了解您。

标签: c# linq ienumerable icollection


【解决方案1】:

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

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

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

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

【讨论】:

  • 哦,这里有区别。谢谢!
  • 请注意,这适用于通用 ICollection<T>,但不适用于非通用 ICollection。对于非通用版本,与IEnumerable 的唯一区别是同步(具有同步根对象)和直接计数。
  • 哦,是的,我是一个和平主义者,所以我根本不接触非泛型,因为我不喜欢拳击。我可能应该扩大答案。
【解决方案2】:

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

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

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

【讨论】:

  • 序列真的可以是无限吗?我认为无限是一个抽象的概念,没有真正的实现来保持其 infinite 的本质不变。如果我们在任何给定时间持有任何序列,它就不再是有限的,因为我能够枚举它。
  • @Veverke 可以有一个无限的IEnumerable 是的,我们从IEnumerable 中需要的只是一个描述如何移动到下一个元素的枚举器。关于如何MoveNext() 的描述可以扩展到无穷大。当然,您不能评估无限 IEnumerable,但您可以评估它的一个子集,在某些情况下,这可能是一种有用的方法。
  • @Veverke:合约只要求实现在请求时提供下一个值。与您所写的相反,您没有“掌握手头的序列”。这是一个无限序列:IEnumerable<int> InfiniteSequence() { while(true) yield return 0; }。尝试枚举该序列。如果您想要实际应用:按顺序提供实时传感器读数。
【解决方案3】:

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

我什么时候需要IEnumerable<T>

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

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

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

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

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

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

非通用版本

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

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

总结

如果您想要惰性评估,请使用IEnumerable<T>。对于渴望评估和不变性,请使用IReadOnlyCollection<T>。对于显式可变性,请使用ICollection<T>.

【讨论】:

  • 感谢您在 IEnumerable 中添加 ICollection 的可变性方面 - 以及它的缺失。
猜你喜欢
  • 2020-01-13
  • 1970-01-01
  • 2016-05-23
  • 1970-01-01
  • 2013-12-07
  • 1970-01-01
  • 2020-02-10
  • 2018-05-10
  • 2014-03-12
相关资源
最近更新 更多