Реализация двунаправленного перечислителя в C#
-
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
который является однонаправленным.