Реализация двунаправленного перечислителя в C#

StackOverflow https://stackoverflow.com/questions/451099

  •  19-08-2019
  •  | 
  •  

Вопрос

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

Это было полезно?

Решение

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

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

Другие советы

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top