Skip的性能(以及类似功能,如Take)

时间:2013-11-15 14:10:57

标签: c# performance linq ienumerable skip-take

我刚看了.NET Framework的Skip / Take扩展方法的源代码(在IEnumerable<T>类型上),发现内部实现正在运行使用GetEnumerator方法:

// .NET framework
    public static IEnumerable<TSource> Skip<TSource>(this IEnumerable<TSource> source, int count)  
    {
        if (source == null) throw Error.ArgumentNull("source"); 
        return SkipIterator<TSource>(source, count); 
    }

    static IEnumerable<TSource> SkipIterator<TSource>(IEnumerable<TSource> source, int count) 
    {
        using (IEnumerator<TSource> e = source.GetEnumerator()) 
        {
            while (count > 0 && e.MoveNext()) count--;
            if (count <= 0) 
            { 
                while (e.MoveNext()) yield return e.Current;
            } 
        } 
    }

假设我有一个IEnumerable<T>,其中包含1000个元素(基础类型为List<T>)。如果我正在做list.Skip(990)会怎么样。拿(10)?在进入最后十个元素之前,它是否会通过990首元素进行迭代? (这就是我的理解)。如果是,那么我不明白为什么微软没有像这样实现Skip方法:

    // Not tested... just to show the idea
    public static IEnumerable<T> Skip<T>(this IEnumerable<T> source, int count)
    {
        if (source is IList<T>)
        {
            IList<T> list = (IList<T>)source;
            for (int i = count; i < list.Count; i++)
            {
                yield return list[i];
            }
        }
        else if (source is IList)
        {
            IList list = (IList)source;
            for (int i = count; i < list.Count; i++)
            {
                yield return (T)list[i];
            }
        }
        else
        {
            // .NET framework
            using (IEnumerator<T> e = source.GetEnumerator())
            {
                while (count > 0 && e.MoveNext()) count--;
                if (count <= 0)
                {
                    while (e.MoveNext()) yield return e.Current;
                }
            }
        }
    }

事实上,他们为Count方法做了例如......

    // .NET Framework...
    public static int Count<TSource>(this IEnumerable<TSource> source) 
    {
        if (source == null) throw Error.ArgumentNull("source");

        ICollection<TSource> collectionoft = source as ICollection<TSource>; 
        if (collectionoft != null) return collectionoft.Count;

        ICollection collection = source as ICollection; 
        if (collection != null) return collection.Count; 

        int count = 0;
        using (IEnumerator<TSource> e = source.GetEnumerator())
        { 
            checked 
            {
                while (e.MoveNext()) count++;
            }
        } 
        return count;
    } 

那是什么原因?

3 个答案:

答案 0 :(得分:13)

在Jon Skeet的优秀教程重新实施Linq中,他(简要地)讨论了这个问题:

  

虽然大多数这些操作都不能明智地优化,但它   在源实现IList时优化Skip是有意义的。   我们可以跳过跳过,可以这么说,直接去   适当的指数。这不会发现源的情况   在迭代之间进行修改,这可能是其中一个原因   据我所知,在框架中实现。

这似乎是推迟优化的合理理由,但我同意,对于特定情况,如果您可以保证您的来源不会/不会被修改,那么进行优化可能是值得的。

答案 1 :(得分:2)

正如ledbutter所提到的,当Jon Skeet reimplemented LINQ时,他提到像Skip这样的优化不会发现在迭代之间修改源的情况“。您可以将代码更改为以下内容,以便检查该情况。它通过在集合的枚举器上调用MoveNext()来实现,即使它不使用e.Current,因此如果集合发生更改,该方法将抛出。

当然,这消除了优化的一个重要部分:枚举器需要被创建,部分步进和处理,但它仍然有一个好处,你不需要毫无意义地逐步完成第一个{{1对象。如果您的count无效,可能会让您感到困惑,因为它指的是e.Current而不是list[i - count]

list[i]

答案 2 :(得分:1)

我认为他们想要在另一个线程中同时修改底层集合时抛出InvalidOperationException“集合被修改...”。你的版本没有这样做。它会产生可怕的结果。

这是MSFT在所有集合中贯穿整个.Net框架的标准做法,这些集合不是线程安全的(虽然有些是特殊的)。