Неправильная реализация компилятором блоков итераторов?

Рассмотрим документацию для IEnumerator.Current:

Current также генерирует исключение, если последний вызов MoveNext вернул false, что указывает на конец коллекции

Однако этого не происходит с блоками итераторов. Например:

void Main()
{
    using (var enumerator = GetCounter().GetEnumerator())
    {
        for (int i = 0; i < 10; i++)
        {
            enumerator.MoveNext();
            Console.WriteLine (enumerator.Current);
        }
    }
}

static IEnumerable<int> GetCounter()
{
   for (int count = 0; count < 3; count++)
   {
       yield return count;
   }
}

Буду просто печатать 8 раз 2без исключений. Глядя на преобразование компилятора, Current это просто свойство на основе поля, которое всегда возвращает значение поля и ничего более. Возможно, это какая-то форма оптимизации? Тем не менее, это выглядит как нарушение договора.

1 ответ

Решение

Пока вы правы для IEnumerator.Current документация, IEnumerator<T>.Current Документация гласит, что свойство не определено для такого сценария. С вашим итератором он возвращает "2". List<T> перечислитель возвращает default(T), а также T[] исключение. Это все допустимые реализации, поскольку они не определены.

Ток не определен ни при одном из следующих условий:

  • Перечислитель располагается перед первым элементом в коллекции, сразу после его создания. MoveNext должен быть вызван для продвижения перечислителя к первому элементу коллекции перед чтением значения Current.
  • Последний вызов MoveNext вернул false, что указывает на конец коллекции.
  • Перечислитель становится недействительным из-за изменений, внесенных в коллекцию, таких как добавление, изменение или удаление элементов.

Стоит отметить, что, хотя он реализует интерфейс, код, сгенерированный из yield return не правильно реализует IEnumerator, так как он продолжает возвращаться 2 в этом случае:

IEnumerator enumerator = GetCounter().GetEnumerator();
for (int i = 0; i < 10; i++)
{
    enumerator.MoveNext();
    Console.WriteLine (enumerator.Current);
}

(для сравнения, List<T> делает ли это правильно: если вы получаете IEnumerator.Current после окончания выдает исключение, и если вы звоните IEnumerator<T>.Current после окончания возвращается default(T))

Другие вопросы по тегам