为IList优化LINQ

不久之前,我编写了一个IList扩展方法,通过使用索引来枚举列表的一部分。 在重构时我意识到可以通过调用Skip(toSkip).Take(amount)来执行类似的查询Skip(toSkip).Take(amount) 。 在对此进行基准测试时,我注意到Skip并未针对IList进行优化。 通过一些谷歌搜索,我最终在Jon Skeet的post, 讨论为什么像Skip这样的优化方法很危险 。

据我理解这篇文章,问题是在修改集合时抛出优化方法时没有exception,但是作为注释声明msdn文档本身就存在冲突。

在IEnumerator.MoveNext()中 :

如果对集合进行了更改,例如添加,修改或删除元素,则枚举数将无法恢复,并且下一次调用MoveNext或Reset 会引发InvalidOperationException。

在IEnumerator.GetEnumerator()中 :

如果对集合进行了更改(例如添加,修改或删除元素),则枚举数将无法恢复,并且其行为未定义

我认为两种惯例都有用,而且无论是否进行优化都会有点失落。 什么是正确的解决方案? 我一直在考虑按照Kris Vandermotten在评论中提到的IList.AssumeImmutable()方式的IList.AssumeImmutable()方法。 是否已存在任何实现,或者这是一个坏主意?

我同意Rafe的观点,即未定义的行为更为正确。 只有版本化的集合才能抛出exception而不是所有集合都是版本化的(数组是最大的例子)。 如果您在调用MoveNext之间进行了2 ^ 32次更改,即使是版本化的集合也可能会出现exception。

假设您真的关心版本控制行为,解决方案是获取IListEnumerator并在每次迭代时调用MoveNext

  public static IEnumerable Skip(this IList source, int count) { using (var e = source.GetEnumerator()) while (count < source.Count && e.MoveNext()) yield return source[count++]; } 

这样,您可以通过索引获得O(1)行为,但仍然可以获得调用MoveNext所有exception抛出行为。 请注意,我们只调用MoveNext来处理exception副作用; 我们忽略它所枚举的值。

ReadOnlyCollection类可能有助于您的不可变集合。

我的建议:除非遇到性能问题,否则我个人不会试图“欺骗”编译器。 您永远不会知道,下一个版本可能会使您的优化代码运行速度比原始代码慢两倍。 不要抢先优化。 框架中提供的方法可以生成一些难以重新实现的真正优化的代码。

这是msdn的一篇文章,它提供了用于不同目的的集合的信息。 我会使用适当的集合来完成任务,而不是尝试优化Skip和Take。