3

を歩いている場合、現在のアイテムの後に残っているアイテムを表すIEnumerable<T>新しいアイテムを取得する方法はありますか。IEnumerable<T>

たとえば、拡張メソッドを記述したいと思いますIEnumerator<T>.Remaining()

IEnumerable<int> sequence = ...
IEnumerator<int> enumerator = sequence.GetEnumerator();

if (enumerator.MoveNext() && enumerator.MoveNext()) {
    IEnumerable<int> rest = enumerator.Remaining();
    // 'rest' would contain elements in 'sequence' start at the 3rd element
}

ある種の単一リンクリストのコレクションを考えているので、残りの要素を表す方法があるはずですよね?IEnumerable<T>どちらかでこれを公開する方法がわからないIEnumerator<T>ので、おそらく、制限のない、非決定的な要素のシーケンスの概念と互換性がありません。

4

4 に答える 4

3

使用する必要があり、使用IEnumerator<T>しない必要がある場合IEnumerable<T>(すべての優れた拡張メソッドがある場合)、ここに2つの簡単なメソッドがあります。

これは1回だけ列挙できます(元の列挙可能オブジェクトにバインドされます。つまり、別のスレッドがソースリストを変更した場合に例外が発生する可能性があります)。

public static IEnumerable<T> Remaining<T>( this IEnumerator<T> value ) {
    while( value.MoveNext() ) {
        yield return value.Current;
    }
}

そして、これはリストを作成し、繰り返し列挙することができます(元の列挙子から切断されているため、ソースのIEnumerableの変更について心配する必要はありません)。

public static IEnumerable<T> Remaining<T>( this IEnumerator<T> value ) {
    List<T> list = new List<T>();
    while( value.MoveNext() ) list.Add( value.Current );

    return list;
}
于 2010-05-13T20:23:26.753 に答える
2

IEnumerator<T>文字通り、シーケンスの残りの部分を表すを取得したい場合はIEnumerable<T>、そこに到達するためにいくつかの魔法を実行する必要があります。

この理由は、一般的な意味で、列挙型は複数回列挙できるのに対し、列挙型はそれ自体が「複数回」の1つにすぎないためです。

まず、処理しているコレクションの種類を把握して、元の列挙子の残りの部分の上に適切な列挙子を返すことができます。あなたが行く理由。

または...列挙子の残りを新しいコレクションにキャッシュして、それを返すことができます。もちろん、これは元の列挙子を消費しますが、それが何であれ、時間やメモリの点で高価になる可能性があります。

または...いくつかの提案を実行できますが、実際には列挙子を返さないでください。代わりに、列挙可能なクラスのSkipメソッドとTakeメソッドを使用して、必要なものを返します。これにより、新しい列挙可能オブジェクトが返されます。列挙されるたびに、元の列挙可能オブジェクトが列挙され、最初の2つの項目がスキップされ、残りが生成されます。

その最後の段落を言い換えさせてください。残りの部分を新しい列挙可能なものとして返そうとせずIEnumerator<T>、代わりに元のコレクションを処理する場合は、処理がはるかに簡単になります。

要素をキャッシュするコードを次に示します。結果の列挙型から2つ以上(または1つだけ)の列挙子を生成し、列挙子が要素内を移動し始めるときに列挙型をスコープ外にすると、ガベージコレクターを開始できるという利点があります。通過した要素を収集します。

言い換えれば、これを行う場合:

var enumerable = enumerator.Remaining();
var enumerator1 = enumerable.GetEnumerator();
var enumerator2 = enumerable.GetEnumerator();

enumerator1.MoveNext();
enumerator2.MoveNext();
<-- at this point, enumerable is no longer used, and the first (head) element
    of the enumerable is no longer needed (there's no way to get to it)
    it can be garbage collected.

もちろん、列挙可能ファイルを保持し、その中のすべての要素を列挙すると、元の列挙可能要素からすべての要素のメモリ内コピーが生成されます。これは、前述のとおり、コストがかかる可能性があります。

とにかく、ここにコードがあります。スレッドセーフではありません:

using System;
using System.Collections.Generic;
using System.Collections;

namespace SO2829956
{
    public class EnumeratorEnumerable<T> : IEnumerable<T>
    {
        private class Node
        {
            public T Value;
            public Node Next;
        }

        private class Enumerator : IEnumerator<T>
        {
            private IEnumerator<T> _Enumerator;
            private Node _Current;

            public Enumerator(IEnumerator<T> enumerator, Node headElement)
            {
                _Enumerator = enumerator;
                _Current = headElement;
            }

            public T Current
            {
                get { return _Current.Value; }
            }

            public void Dispose()
            {
                _Enumerator.Dispose();
            }

            object IEnumerator.Current
            {
                get { return Current; }
            }

            public bool MoveNext()
            {
                if (_Current.Next != null)
                {
                    _Current = _Current.Next;
                    return true;
                }
                else if (_Enumerator.MoveNext())
                {
                    _Current.Next = new Node
                    {
                        Value = _Enumerator.Current
                    };
                    _Current = _Current.Next;
                    return true;
                }
                else
                {
                    _Enumerator.Dispose();
                    return false;
                }
            }

            public void Reset()
            {
                throw new NotImplementedException();
            }
        }

        private IEnumerator<T> _Enumerator;
        private Node _FirstElement;

        public EnumeratorEnumerable(IEnumerator<T> enumerator)
        {
            _Enumerator = enumerator;
            _FirstElement = new Node
            {
                Next = null,
                Value = enumerator.Current
            };
        }

        public IEnumerator<T> GetEnumerator()
        {
            return new Enumerator(_Enumerator, _FirstElement);
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }

    public static class EnumeratorExtensions
    {
        public static IEnumerable<T> Remaining<T>(
            this IEnumerator<T> enumerator)
        {
            return new EnumeratorEnumerable<T>(enumerator);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            List<int> values = new List<int> { 1, 2, 3, 4, 5 };
            IEnumerator<int> enumerator = values.GetEnumerator();
            enumerator.MoveNext();
            enumerator.MoveNext();

            var enumerable = enumerator.Remaining();
            foreach (var i in enumerable)
                Console.Out.WriteLine(i);
            foreach (var i in enumerable)
                Console.Out.WriteLine(i);
        }
    }
}

このプログラムを実行した結果は次のとおりです。

3
4
5
3
4
5
于 2010-05-13T20:27:57.277 に答える
2

TakeSkipは、使用する2つの方法です。

IEnumerable<int> sequence = ...
IEnumerable<int> pair = sequence.Take(2); //First two elements
IEnumerable<int> remaining = sequence.Skip(2);
于 2010-05-13T20:20:56.450 に答える
0

あなたの目標が直接使用できるようにすることであるならばforeachIEnumerator<T>私はこのような何かを提案するでしょう:

public struct WrappedEnumerator<T>
{
    T myEnumerator;
    public T GetEnumerator() { return myEnumerator; }
    public WrappedEnumerator(T theEnumerator) { myEnumerator = theEnumerator; }
}
public static class AsForEachHelper
{
    static public WrappedEnumerator<IEnumerator<T>> AsForEach<T>(this IEnumerator<T> theEnumerator)
        { return new WrappedEnumerator<IEnumerator<T>>(theEnumerator);}

    static public WrappedEnumerator<System.Collections.IEnumerator> AsForEach(this System.Collections.IEnumerator theEnumerator) 
        { return new WrappedEnumerator<System.Collections.IEnumerator>(theEnumerator); }

    [Obsolete("Structs implementing IEnumerator<T> should be boxed before use", false)]
    static public WrappedEnumerator<System.Collections.IEnumerator> AsForEach<T>(this T theEnumerator) where T : struct, System.Collections.IEnumerator 
    { return new WrappedEnumerator<System.Collections.IEnumerator>(theEnumerator) ; }
}

が、、、またはいずれかから派生した任意のクラス型のfoo変数である場合、単に;と言うことができます。ループが早期に終了した場合、読み取られなかったアイテムはすべて列挙子に残ります。ただし、where is aのようなステートメントは、メソッドと互換性のない構造体タイプとして定義されることに注意してください。本当に勇気がある場合は、タグを削除して(またはそのパラメーターをに変更して)、ボックス化されていない列挙型で使用できるようにすることができますが、構造体型の列挙型を呼び出すと、列挙状態のスナップショットが取得され、列挙される可能性があることに注意してください。そのスナップショットは、元の状態に影響を与えない可能性があります。IEnumeratorIEnumerator<T>foreach (whatever in foo.AsForEach())myEnumerator Foo=someList.GetEnumerator()someListList<T>myEnumeratorWrappedEnumerator<T>ObsoletefalseAsForEachAsForEach

于 2012-10-01T20:51:24.313 に答える