Performance of Skip (and similar functions, like Take)

旧时模样 提交于 2019-11-27 21:58:21

In Jon Skeet's excellent tutorial re-implementing Linq, he discusses (briefly) that very question:

Although most of these operations can't be sensibly optimized, it would make sense to optimize Skip when the source implements IList. We can skip the skipping, so to speak, and go straight to the appropriate index. This wouldn't spot the case where the source was modified between iterations, which may be one reason it's not implemented in the framework as far as I'm aware.

That seems like a reasonable reason to hold off on that optimization, but I agree that for specific cases, it may be worthwhile to make that optimization if you can guarantee your source can't/won't be modified.

As ledbutter mentioned, when Jon Skeet reimplemented LINQ, he mentioned that an optimization like your Skip "wouldn't spot the case where the source was modified between iterations". You can change your code to the following to make it check for that case. It does so by calling MoveNext() on the collection's enumerator, even though it doesn't use e.Current, so that the method will throw if the collection changes.

Granted, this removes a significant part of the optimization: that the enumerator needs to be created, partially stepped through, and disposed, but it still has the benefit that you don't need to pointlessly step through the first count objects. And it might be confusing that you have an e.Current that is not useful, since it points to list[i - count] instead of list[i].

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

I assume that they wanted to throw InvalidOperationException "Collection was modified..." when the underlying collection is modified meanwhile in another thread. Your version doesn't do that. It will yield terrible results.

That is the standard practice MSFT is following throughout .Net framework in all Collections which is not thread safe(some are exceptional though).

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!