为什么我不应该总是使用 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>.
我在 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>.