Неправильная реализация компилятором блоков итераторов?
Рассмотрим документацию для 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)
)