13

IEnumerator<T>いくつかの状態を維持する必要があり、それを単純化するために反復子ブロックを使用する必要があるカスタム実装を作成するにはどうすればよいですか? 私が思いつくことができる最高のものは、次のようなものです:

public class MyEnumerator<T> : IEnumerator<T> {
    private IEnumerator<T> _enumerator;
    public int Position {get; private set;} // or some other custom properties

    public MyEnumerator() {
        Position = 0;
        _enumerator = MakeEnumerator();
    }

    private IEnumerator<T> MakeEnumerator() {
        // yield return something depending on Position
    } 

    public bool MoveNext() {
        bool res = _enumerator.MoveNext();
        if (res) Position++;
        return res;
    }

    // delegate Reset and Current to _enumerator as well
}

public class MyCollection<T> : IEnumerable<T> {

    IEnumerator<T> IEnumerable<T>.GetEnumerator() {
        return GetEnumerator();
    }

    public MyEnumerator<T> GetEnumerator() {
        return new MyEnumerator<T>();
    }

    ...
}
4

4 に答える 4

34

なぜ反復子クラスを書きたいのですか? イテレータブロックの要点は、そうする必要がないことです...

すなわち

public IEnumerator<T> GetEnumerator() {
    int position = 0; // state
    while(whatever) {
        position++;
        yield return ...something...;
    }
}

さらにコンテキスト (つまり、上記が機能しない理由) を追加すると、おそらくさらに多くのことができるようになります。

ただし、可能であれば、反復子クラスを作成することは避けてください。それらは多くの作業であり、間違いやすいです。

ところで、実際に気にする必要はありませんReset- それはほとんど非推奨であり、実際には使用すべきではありません (任意の列挙子に対して動作することに依存できないため)。

内部イテレータを消費したい場合は、それも問題ありません。

int position = 0;
foreach(var item in source) {
   position++;
   yield return position;
}

または、列挙子しかない場合:

while(iter.MoveNext()) {
   position++;
   yield return iter.Current;
}

また、生成するものに状態を (タプルとして) 追加することを検討することもできます。

class MyState<T> {
    public int Position {get;private set;}
    public T Current {get;private set;}
    public MyState(int position, T current) {...} // assign
}
...
yield return new MyState<Foo>(position, item);

最後に、LINQ スタイルの拡張機能/デリゲート アプローチを使用してAction<int,T>、位置と値を呼び出し元に提供できます。

    static void Main() {
        var values = new[] { "a", "b", "c" };
        values.ForEach((pos, s) => Console.WriteLine("{0}: {1}", pos, s));            
    }
    static void ForEach<T>(
            this IEnumerable<T> source,
            Action<int, T> action) {
        if (source == null) throw new ArgumentNullException("source");
        if (action == null) throw new ArgumentNullException("action");

        int position = 0;
        foreach (T item in source) {
            action(position++, item);
        }
    }

出力:

0: a
1: b
2: c
于 2009-01-11T08:59:11.730 に答える
2

ここでマークに同意する必要があります。本当に必要な場合は列挙子クラスを完全に自分で作成するか (単にできるという理由だけで?)、単にインタレータ ブロックと yield ステートメントを使用して、それで完了します。個人的には、列挙子クラスには二度と触れません。;-)

于 2009-01-11T09:10:16.357 に答える
1

ほとんどの (実際にはすべての) 作業を行うために、デフォルトの Enumerator を借用する非常に単純な Iterator を作成しました。コンストラクターは を受け取り、IEnumerator<T>私の実装は単純にそれに作業を渡します。Indexカスタム Iterator にフィールドを追加しました。

ここで簡単な例を作成しました: https://dotnetfiddle.net/0iGmVz

この Iterator セットアップを使用するには、カスタム Collection/List クラスに次のようなものを用意します。

public class MyList<T> : List<T>{
    public new IEnumerator<T> GetEnumerator(){
        return new IndexedEnumerator<T>(base.GetEnumerator());
    }
}

これforeachで、他の組み込み関数はカスタム列挙子を取得し、オーバーライドしたくない動作は通常の実装を使用します。

public static class Helpers{
    //Extension method to get the IndexEnumerator
    public static IndexedEnumerator<T> GetIndexedEnumerator<T>(this IEnumerable<T> list){
        return new IndexedEnumerator<T>(list.GetEnumerator());
    }
}

//base Enumerator methods/implementation
public class BaseEnumerator<T> : IEnumerator<T>{
    public BaseEnumerator(IEnumerator<T> enumer){
        enumerator = enumer;
    }

    protected virtual IEnumerator<T> enumerator{get;set;}

    protected virtual T current {get;set;}

    public virtual bool MoveNext(){
        return enumerator.MoveNext();
    }

    public virtual IEnumerator<T> GetEnumerator(){
        return enumerator;
    }

    public virtual T Current {get{return enumerator.Current;}}

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

    public virtual void Reset(){}

    public virtual void Dispose(){}
}

public class IndexedEnumerator<T> : BaseEnumerator<T>
{
    public IndexedEnumerator(IEnumerator<T> enumer):base(enumer){}

    public int Index {get; private set;}

    public override bool MoveNext(){
        Index++;
        return enumerator.MoveNext();
    }
}
于 2016-03-15T18:58:07.350 に答える
1

@マーク・グラベル

ただし、可能であれば、反復子クラスを作成することは避けてください。それらは多くの作業であり、間違いやすいです。

だからこそyield、イテレーター内で機械を使用して、重いものを持ち上げたいのです。

また、生成するものに状態を (タプルとして) 追加することを検討することもできます。

はい、うまくいきます。ただし、これはすべてのステップでの追加の割り当てです。ほとんどのステップのみに関心Tがある場合、それを回避できれば必要のないオーバーヘッドです。

しかし、あなたの最後の提案は私にアイデアを与えました:

public IEnumerator<T> GetEnumerator(Action<T, int> action) {
    int position = 0; // state
    while(whatever) {
        position++;
        var t = ...something...;
        action(t, position);
        yield return t;
    }
}

public IEnumerator<T> GetEnumerator() {
    return GetEnumerator(DoNothing<T, int>());
}
于 2009-01-11T10:18:59.730 に答える