对于大多数开发人员,IList
并ICollection
暗示您有一个预先评估的内存中的集合可以使用。IList
具体而言,存在恒定时间Add
*和索引操作的隐式合同。这就是为什么LinkedList<T>
不实施IList<T>
。我认为 FibonacciList 违反了这个隐含的合同。
请注意最近 MSDN 杂志文章中的以下段落,该文章讨论了将只读集合接口添加到 .NET 4.5 的原因:
IEnumerable<T>
对于大多数处理类型集合的场景来说已经足够了,但有时你需要比它提供的更多的功能:
- 物化:
IEnumerable<T>
不允许您表达集合是否已经可用(“物化”)或是否在每次迭代时都计算(例如,如果它表示 LINQ 查询)。当一个算法需要对集合进行多次迭代时,如果计算序列的成本很高,这可能会导致性能下降;当在后续传递中再次生成对象时,它还可能由于身份不匹配而导致细微的错误。
正如其他人所指出的,还有一个问题是你会返回什么.Count
。
对于这样的数据集合使用IEnumerable
或输入是完全可以IQueryable
的,因为人们期望这些类型可以被懒惰地评估。
关于编辑1:.Count()
不是由IEnumerable<T>
接口实现的:它是一个扩展方法。因此,开发人员需要预计它可能会花费任何时间,并且他们需要避免在他们实际上不需要知道项目数量的情况下调用它。例如,如果您只想知道 an 是否IEnumerable<T>
有任何项目,最好使用.Any()
. 如果您知道要处理的项目数量上限,则可以使用.Take()
. 如果一个集合中有多个int.MaxValue
项目,.Count()
会遇到操作溢出。因此,有一些解决方法可以帮助减少与无限序列相关的危险。显然,如果程序员没有考虑到这些可能性,它仍然会导致问题。
关于编辑 2:如果您计划以索引是恒定时间的方式实现您的序列,那么这很容易解决我的主要观点。不过,Sixlettervariables 的答案仍然成立。
*显然还有更多:只有在返回Add
时才有望工作。只有在返回 false 等情况下才可能进行修改,等等。这是一个经过深思熟虑的接口:这个事实最终可以通过在 .NET 4.5 中引入只读集合接口来纠正。IList.IsFixedSize
false
IsReadOnly
IList
更新
考虑到这一点,我个人认为IEnumerable<>
s 也不应该是无限的。除了物化方法,如.ToList()
,LINQ 有几个非流式操作,如在返回第一个结果之前.OrderBy()
必须消耗整个操作。IEnumerable<>
由于这么多方法都假设IEnumerable<>
s 可以安全地遍历它们,因此产生一个IEnumerable<>
本质上不安全的无限期遍历将违反 Liskov 替换原则。
如果您发现您的应用程序经常需要斐波那契数列的片段作为 IEnumerables,我建议创建一个具有类似于 签名的方法Enumerable.Range(int, int)
,它允许用户定义开始和结束索引。
如果您想开始一个 Gee-Whiz 项目,您可以想象开发一个基于 Fibonacci 的IQueryable<>
提供程序,用户可以在其中使用有限的 LINQ 查询语法子集,如下所示:
// LINQ to Fibonacci!
var fibQuery = from n in Fibonacci.Numbers // (returns an IQueryable<>)
where n.Index > 5 && n.Value < 20000
select n.Value;
var fibCount = fibQuery.Count();
var fibList = fibQuery.ToList();
由于您的查询提供者有权将where
子句评估为 lambda 表达式,因此您可以有足够的控制权来实现Count
方法,并.GetEnumerator()
确保查询的限制性足以产生真正的答案,或者尽快抛出异常该方法被调用。
但这听起来很聪明,对于任何现实生活中的软件来说都可能是一个非常糟糕的主意。