Написание собственного IEnumerator <T>с итераторами - PullRequest
12 голосов
/ 11 января 2009

Как я могу написать собственную реализацию 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 ]

32 голосов
/ 11 января 2009

Почему вы хотите написать класс итератора? Весь смысл блока итератора в том, что вам не нужно ...

т.е.

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

Если вы добавите больше контекста (например, почему вышеупомянутое не может работать), мы, вероятно, можем помочь больше.

Но, если возможно, избегайте написания класса итератора. Они много работают, и их легко ошибиться.

Кстати, вам не нужно беспокоиться о Reset - он в значительной степени устарел и его никогда не следует использовать (поскольку на него нельзя полагаться для работы с произвольным перечислителем). 1011 *

Если вы хотите использовать внутренний итератор, это тоже хорошо:

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
2 голосов
/ 11 января 2009

Я должен согласиться с Марком здесь. Либо полностью напишите класс перечислителя, если вы действительно этого хотите (просто потому, что можете?), Либо просто используйте блок интегратора и выведите операторы и покончите с этим. Лично я никогда больше не трогаю классы перечислителей. ; -)

1 голос
/ 15 марта 2016

Я сделал довольно простой итератор, который позаимствовал Enumerator по умолчанию для выполнения большей части (действительно всей) работы. Конструктор занимает IEnumerator<T>, и моя реализация просто передает ему работу. Я добавил поле Index в свой пользовательский итератор.

Я сделал простой пример здесь: https://dotnetfiddle.net/0iGmVz

Чтобы использовать эту настройку Итератора, в вашем пользовательском классе 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();
    }
}
1 голос
/ 11 января 2009

@ Марк Гравелл

Но, если возможно, избегайте написания класса итератора. Они много работают, и их легко ошибиться.

Именно поэтому я хочу использовать механизм 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>());
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...