Реализация двунаправленного перечислителя в C # - PullRequest
4 голосов
/ 16 января 2009

Есть ли способ использовать блоки доходности для реализации IEnumerator<T>, который может идти как назад (MoveLast()), так и вперед?

Ответы [ 7 ]

5 голосов
/ 16 января 2009

Нет, конечный автомат, сгенерированный компилятором C #, строго вперед.

Во многих случаях даже нет смысла возвращаться назад. Представьте себе, что итератор читает из сетевого потока - чтобы вернуться назад, ему нужно будет запомнить все, что он когда-либо читал, потому что он не может перемотать время и снова запросить данные у сети.

(То же самое, что генерировало данные каким-то образом с потерями. Представьте себе итератор, который возвращал новую доску для жизни Конвея на каждой итерации - есть несколько плат, которые могли бы быть предыдущей , так что вернуться назад, вы снова должны вспомнить, что вы уже вернули.)

4 голосов
/ 16 января 2009

Не напрямую из блока итератора, нет.

Однако вызывающая сторона всегда может буферизовать результаты, например, в List<T> или просто вызвать Reverse() - но это не всегда применимо.

3 голосов
/ 22 апреля 2015

Я знаю, что эта тема очень старая, но важно отметить, что

foreach(var item in someCollection)
{
    // Do something
}

... компилируется в:

var enumerator = someCollection.GetEnumerator()
while (enumerator.MoveNext())
{
    var item = enumerator.Current;
    // Do something
}

Так что, если вы не возражаете против синтаксиса MoveNext, вы можете легко реализовать IEnumerator и добавить MovePrevious. Вы не сможете изменить направление, если будете использовать foreach, но вы сможете изменить направление, если используете цикл while.

Или ... если вы хотите "просматривать" список в обратном (не двунаправленном) списке, вы можете воспользоваться инструкцией доходности.

public static IEnumerable<TItem> Get<TItem>(IList<TItem> list)
{
    if (list == null)
        yield break;

    for (int i = list.Count - 1; i > -1; i--)
        yield return list[i];
}

Или ... если вы хотите выполнить обратный поиск, пройдя длинный путь, вы можете реализовать свой собственный IEnumerable / IEnumerator

public static class ReverseEnumerable
{
    public static IEnumerable<TItem> Get<TItem>(IList<TItem> list)
    {
        return new ReverseEnumerable<TItem>(list);
    }
}

public struct ReverseEnumerable<TItem> : IEnumerable<TItem>
{
    private readonly IList<TItem> _list;

    public ReverseEnumerable(IList<TItem> list)
    {
        this._list = list;
    }

    public IEnumerator<TItem> GetEnumerator()
    {
        if (this._list == null)
            return Enumerable.Empty<TItem>().GetEnumerator();

        return new ReverseEnumator<TItem>(this._list);
    }

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

public struct ReverseEnumator<TItem> : IEnumerator<TItem>
{
    private readonly IList<TItem> _list;
    private int _currentIndex;

    public ReverseEnumator(IList<TItem> list)
    {
        this._currentIndex = list.Count;
        this._list = list;
    }

    public bool MoveNext()
    {
        if (--this._currentIndex > -1)
            return true;

        return false;
    }

    public void Reset()
    {
        this._currentIndex = -1;
    }

    public void Dispose() { }

    public TItem Current
    {
        get
        {
            if (this._currentIndex < 0)
                return default(TItem);

            if (this._currentIndex >= this._list.Count)
                return default(TItem);

            return this._list[this._currentIndex];
        }
    }

    object IEnumerator.Current
    {
        get { return this.Current; }
    }
}
1 голос
/ 16 января 2009

На самом деле, похоже, что подход описан в Ускоренный C # 2008 . К сожалению, две страницы не видны в предварительном просмотре, и он должен полагаться на отражение (результаты которого можно кэшировать, как обычно), но вы можете получить суть.

1 голос
/ 16 января 2009

Нет. Одним из ограничений IEnumerator является то, что он хранит свое текущее состояние и не помнит свое предыдущее состояние. В результате IEnumerable доступен только для пересылки.

Если вам нужно придерживаться предыдущих состояний, прочитайте IEnumerable в List или LinkedList и перечислите все эти объекты.

1 голос
/ 16 января 2009

C5 Библиотека коллекций (http://www.itu.dk/research/c5/) реализует коллекции и связанный список с обратным перечислением. Проект с открытым исходным кодом, поэтому вы сможете найти ответ там.

0 голосов
/ 16 января 2009

Нет. Использование yield приводит к IEnumerable, который является однонаправленным.

...