9

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

据我对这篇文章的理解,问题是在修改集合时优化方法中不会抛出异常,但正如评论所述,msdn 文档本身存在冲突。

IEnumerator.MoveNext()中:

如果对集合进行了更改,例如添加、修改或删除元素,则枚举器将不可恢复地失效,并且下一次调用 MoveNext 或 Reset将引发 InvalidOperationException。

IEnumerator.GetEnumerator()中:

如果对集合进行了更改,例如添加、修改或删除元素,则枚举器将不可恢复地失效并且其行为是未定义的

我在这两种约定中都看到了优点,并且对于是否进行优化有点迷茫。什么是合适的解决方案?我一直在考虑采用 Kris Vandermotten 在评论中提到的IList.AssumeImmutable()方法。AsParallel()是否已经存在任何实现,或者这是一个坏主意?

4

2 回答 2

3

我同意 Rafe 的观点,即未定义的行为更正确。只有版本化的集合可以抛出异常,并不是所有的集合都是版本化的(数组是最大的例子)。如果您在对MoveNext.

假设您真的关心版本控制行为,解决方案是获取一个Enumeratorfor并在每次迭代时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异常副作用;我们忽略它枚举的值。

于 2011-05-03T05:38:42.577 回答
0

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

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

是来自 msdn 的一篇文章,其中提供了有关将哪些集合用于不同目的的信息。我会为任务使用适当的集合,而不是尝试优化 Skip and Take。

于 2011-05-03T04:58:08.723 回答