Подтвердить что ты не робот

Возможность reset IEnumerator, сгенерированный с использованием yield (С#)

Если я использую yield вместо ручного создания IEnumerator, возможно ли реализовать IEnumerator.Reset?

4b9b3361

Ответ 1

Нет встроенной поддержки, но вы можете определить собственную реализацию IEnumerator, которая делегирует все вызовы методов перечислителю, сгенерированному С#, и позволяет вам определять ваше собственное поведение для метода Reset.

Простейшая версия класса будет выглядеть так:

class ResetableEnumerator<T> : IEnumerator<T>
{
  public IEnumerator<T> Enumerator { get; set; }
  public Func<IEnumerator<T>> ResetFunc { get; set; }

  public T Current { get { return Enumerator.Current; } }
  public void  Dispose() { Enumerator.Dispose(); }
  object IEnumerator.Current { get { return Current; } }
  public bool  MoveNext() { return Enumerator.MoveNext(); }
  public void  Reset() { Enumerator = ResetFunc(); }
}

В этом случае указанный ResetFunc возвращает новый IEnumerator<T>, поэтому предоставленная вами реализация ResetFunc может выполнить некоторую очистку или что-то еще, что вам нужно сделать при сбросе, а затем вернуть новый счетчик.

IEnumerator<int> Foo() { /* using yield return */ }
IEnumerator<int> PublicFoo() {
  return new ResetableEnumerator<int> { 
    Enumerator = Foo(),
    ResetFunc = () => { 
      Cleanup();
      return Foo(); } };
}

Вам нужно будет сохранить все исходные локальные переменные метода Foo в качестве полей этого класса, чтобы вы могли получить к ним доступ в Cleanup (обратите внимание, что остальная часть тела Foo никогда не будет выполняется после вызова Reset), но это все же проще, чем писать рукописный итератор!

Ответ 2

Нет, это невозможно. Когда компилятор С# обрабатывает итератор (метод, содержащий оператор yield), компилятор генерирует класс, который реализует IEnumerable и IEnumerator. Сгенерированная реализация класса Reset просто генерирует исключение NotSupportedException. Невозможно повлиять на это в текущих версиях С#.

Вместо этого ваш код вызова должен будет запросить новый счетчик, т.е. начать новый цикл foreach. Или вам нужно отказаться от поддержки языка (инструкция yield) и написать свой собственный класс, который реализует IEnumerator.

Ответ 3

Хорошее обходное решение, которое я только что обнаружил. Сделайте свой метод генератора IEnumerable, не IEnumerator. Затем вы можете сделать

var values = MyGeneratorMethod();
var enumerator = values.GetEnumerator();
// ... do stuff with enumerator
enumerator = values.GetEnumerator(); // instead of enumerator.Reset();

Я считаю, что ответ itowlson предложил этот точный трюк, но я не мог понять его, пока не услышал об этом в другом месте.

Ответ 4

Вы могли бы добавить логику, которая при выполнении сбрасывается путем выпуска yield break;, который выходит из блока итератора, не возвращая никаких элементов. Например:

public IEnumerable<int> GetLimitedIntegers(int limit)
{
    foreach (int i in Enumerable.Range(1, 10))
    {
        if (i < limit)
            yield return i;
        else
            yield break;
    }
}

Затем используйте его:

foreach(int i in GetLimitedIntegers(6))
    Console.WriteLine(i);

Ответ 5

Этот код реализует атрибут, который может быть установлен на reset перечислитель. Примечания:

  • К сожалению, этот метод сброса не будет работать с несколькими экземплярами счетчиков
  • Это не reset через интерфейс, но фактически через настройку свойства либо напрямую, либо через метод
  • Учитывая сложность и хрупкость, я бы не рекомендовал этот подход
  • Мне действительно интересно, может ли goto быть опрятным

код:

public IEnumerator<Entry> GetEnumerator(){
    do{
        this.shouldReset=FALSE;
        for (Entry e = this.ReadEntry(); e != null; e = this.ReadEntry()){
            if(self.shouldReset)break;
            else yield return e;
        }
    }
    while(self.shouldReset)
}