25

.NET Framework (型) のSkip/拡張メソッドのソース コードを調べたところ、内部実装がメソッドで動作していることがわかりました。TakeIEnumerable<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).Take(10) を実行するとどうなりますか? 最後の 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;
    } 

では、その理由は何ですか?

4

3 に答える 3

17

Linqを再実装する Jon Skeet の優れたチュートリアルで、彼はまさにその質問について (簡単に) 説明しています。

これらの操作のほとんどは賢明に最適化することはできませんが、ソースが IList を実装している場合は Skip を最適化することは理にかなっています。いわばスキップをスキップして、適切なインデックスに直接進むことができます。これは、反復の間にソースが変更されたケースを見つけられません。これは、私が知る限り、フレームワークに実装されていない理由の 1 つかもしれません。

その最適化を延期する合理的な理由のように思えますが、特定のケースでは、ソースが変更できない/変更されないことを保証できる場合は、その最適化を行う価値があることに同意します。

于 2013-11-15T14:27:16.000 に答える
3

ledbutter が言及したように、Jon Skeet がLINQ を再実装したとき、彼はあなたのような最適化Skipでは「ソースが反復間で変更されたケースを見つけられない」と述べました。コードを次のように変更して、そのケースをチェックすることができます。MoveNext()を使用していなくても、コレクションの列挙子を呼び出すことe.Currentでこれを行い、コレクションが変更された場合にメソッドがスローされるようにします。

count確かに、これにより最適化の重要な部分が取り除かれます。つまり、列挙子を作成し、部分的にステップ スルーし、破棄する必要がありますが、最初のオブジェクトを無意味にステップ スルーする必要がないという利点があります。の代わりに をe.Current指しているので、役に立たないがあると混乱するかもしれません。list[i - count]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;
            }
        }
    }
}
于 2013-11-15T14:40:01.180 に答える
0

InvalidOperationException基になるコレクションが別のスレッドでその間に変更されたときに、「コレクションが変更されました...」をスローしたいと思います。あなたのバージョンはそれをしません。恐ろしい結果をもたらします。

これは、スレッド セーフではないすべてのコレクションの .Net フレームワーク全体で MSFT が従う標準的な方法です (例外的なものもあります)。

于 2013-11-15T14:27:08.710 に答える