使用迭代器</t>编写自定义IEnumerator <t>

时间:2009-01-11 08:49:22

标签: c# iterator

如何编写需要维护某些状态的自定义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 个答案:

答案 0 :(得分:32)

为什么要编写迭代器类?迭代器块的重点是你不必......

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

答案 1 :(得分:2)

我必须在这里同意Marc。如果你真的想要自己写一个枚举器类(只是因为你可以?)或者只是使用一个interator块和yield语句并完成它。就个人而言,我再也不会接触普查员班了。 ; - )

答案 2 :(得分:1)

@Marc Gravell

  

但是如果可能的话,避免编写迭代器类。他们工作很多,容易出错。

这正是我想在迭代器中使用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>());
}

答案 3 :(得分:1)

我做了一个非常简单的迭代器,它借用了默认的Enumerator来完成大部分(全部)工作。构造函数使用IEnumerator<T>,我的实现只需将其交给工作。我在自定义迭代器中添加了Index字段。

我在这里做了一个简单的例子: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();
    }
}