C# 使用迭代器编写自定义 IEnumerator<T>

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/432600/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-04 03:10:00  来源:igfitidea点击:

Writing custom IEnumerator<T> with iterators

c#iterator

提问by Alexey Romanov

How can I write a custom IEnumerator<T>implementation which needs to maintain some state and still get to use iterator blocks to simplify it? The best I can come up with is something like this:

如何编写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>();
    }

    ...
}

采纳答案by Marc Gravell

Why do you want to write an iterator class? The whole point of an iterator block is so you don't have to...

为什么要编写迭代器类?迭代器块的全部意义在于,您不必...

i.e.

IE

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

If you add more context (i,e, why the above can't work), we can probably help more.

如果您添加更多上下文(即,为什么上述方法不起作用),我们可能会提供更多帮助。

But if possible, avoid writing an iterator class. They are lots of work, and easy to get wrong.

但如果可能,请避免编写迭代器类。它们工作量很大,而且很容易出错。

By the way, you don't really have to bother with Reset- it is largely deprecated, and shouldn't really ever be used (since it can't be relied to work for an arbitrary enumerator).

顺便说一句,你真的不必费心Reset- 它在很大程度上已被弃用,并且不应该真正使用(因为它不能用于任意枚举器)。

If you want to consume an inner iterator, that is fine too:

如果您想使用内部迭代器,那也可以:

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

or if you only have an enumerator:

或者如果您只有一个枚举器:

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

You might also consider adding the state (as a tuple) to the thing you yield:

您还可以考虑将状态(作为元组)添加到您产生的事物中:

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);

Finally, you could use a LINQ-style extension/delegate approach, with an Action<int,T>to supply the position and value to the caller:

最后,您可以使用 LINQ 样式的扩展/委托方法,并使用 anAction<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);
        }
    }

Outputs:

输出:

0: a
1: b
2: c

回答by peSHIr

I'd have to concur with Marc here. Either write an enumerator class completely yourself if you really want to (just because you can?) or simply use an interator block and yield statements and be done with it. Personally, I'm never touching enumerator classes again. ;-)

我不得不同意 Marc 的意见。如果你真的想要(仅仅因为你可以?)或者只是使用一个interator块和yield语句并完成它,要么完全自己编写一个枚举类。就个人而言,我再也不会接触枚举类了。;-)

回答by Alexey Romanov

@Marc Gravell

@马克格拉维尔

But if possible, avoid writing an iterator class. They are lots of work, and easy to get wrong.

但如果可能,请避免编写迭代器类。它们工作量很大,而且很容易出错。

That's precisely why I want to use yieldmachinery inside my iterator, to do the heavy lifting.

这正是我想yield在迭代器中使用机器来完成繁重工作的原因。

You might also consider adding the state (as a tuple) to the thing you yield:

您还可以考虑将状态(作为元组)添加到您产生的事物中:

Yes, that works. However, it's an extra allocation at each and every step. If I am interested only in Tat most steps, that's an overhead I don't need if I can avoid it.

是的,这有效。但是,这是每一步的额外分配。如果我只对T大多数步骤感兴趣,那么如果我可以避免的话,这是我不需要的开销。

However, your last suggestion gave me an idea:

但是,您的最后一个建议给了我一个想法:

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>());
}

回答by Chakrava

I made a pretty simple Iterator that borrows the default Enumerator to do most (really all) of the work. The constructor takes an IEnumerator<T>and my implementation simply hands it the work. I've added an Indexfield to my custom Iterator.

我制作了一个非常简单的迭代器,它借用默认的 Enumerator 来完成大部分(实际上是所有)工作。构造函数接受一个,IEnumerator<T>而我的实现只是简单地将工作交给它。我在Index我的自定义迭代器中添加了一个字段。

I made a simple example here: https://dotnetfiddle.net/0iGmVz

我在这里做了一个简单的例子:https: //dotnetfiddle.net/0iGmVz

To use this Iterator setup you would have something like this in your custom Collection/List class:

要使用此 Iterator 设置,您将在自定义 Collection/List 类中具有以下内容:

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

Now foreachand other built-in functions will get your custom enumerator, and any behavior you don't want to override will use the normal implementation.

Nowforeach和其他内置函数将获取您的自定义枚举器,并且您不想覆盖的任何行为都将使用正常实现。

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();
    }
}