为IList优化LINQ

时间:2011-05-02 21:08:37

标签: c# .net linq optimization skip

前段时间我编写了一个IList扩展方法,通过使用索引来枚举列表的一部分。在重构时,我意识到可以通过调用Skip(toSkip).Take(amount)来执行类似的查询。在对此进行基准测试时,我注意到Skip未针对IList进行优化。通过一些谷歌搜索,我最终在Jon Skeet帖子discussing why optimizing methods like Skip is dangerous

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

IEnumerator.MoveNext()中:

  

如果对集合进行了更改,   例如添加,修改或删除   元素,枚举器是   无可挽回地失效和下一个   调用MoveNext或重置会抛出一个   InvalidOperationException异常。

IEnumerator.GetEnumerator()中:

  

如果对集合进行了更改,   例如添加,修改或删除   元素,枚举器是   不可恢复的无效及其   行为未定义

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

2 个答案:

答案 0 :(得分:2)

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

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

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

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

答案 1 :(得分:0)

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

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

here是来自msdn的文章,提供了有关用于不同目的的集合的信息。我会使用适当的集合来完成任务,而不是尝试优化Skip and Take。

相关问题