Я бы подумал, что выполнение следующего кода для пустой коллекции, реализующей IEnumerable<T>
, вызовет исключение:
var enumerator = collection.GetEnumerator();
enumerator.MoveNext();
var type = enumerator.Current.GetType(); // Surely should throw?
Поскольку коллекция пуста, доступ к IEnumerator.Current
недопустим, и я ожидал бы исключения. Однако для List<T>
исключение не исключено.
Это разрешено документацией для IEnumerator<T>.Current
, которая гласит, что Current
есть undefined при любом из следующих условий:
- Перечислитель помещается перед первым элементом в коллекции сразу после создания счетчика. MoveNext должен быть вызван для продвижения счетчика к первому элементу коллекции перед чтением значения Current.
- Последний вызов MoveNext возвращает false, что указывает на конец коллекции.
- Перечислитель недействителен из-за изменений, внесенных в коллекцию, таких как добавление, изменение или удаление элементов.
(Я предполагаю, что "не удается исключить исключение" можно классифицировать как "undefined поведение"...)
Однако, если вы сделаете то же самое, но вместо этого используйте IEnumerable
, вы получите исключение. Это поведение указано документацией для IEnumerator.Current
, которая гласит:
- Current должен вызывать InvalidOperationException, если последний вызов MoveNext возвратил false, что указывает на конец коллекции.
Мой вопрос: зачем эта разница? Есть ли хорошая техническая причина, о которой я не знаю?
Это означает, что идентично выглядящий код может вести себя по-разному в зависимости от того, использует ли он IEnumerable<T>
или IEnumerable
, как демонстрирует следующая программа (обратите внимание, как код внутри showElementType1()
и showElementType1()
идентичен):
using System;
using System.Collections;
using System.Collections.Generic;
namespace ConsoleApplication2
{
class Program
{
public static void Main()
{
var list = new List<int>();
showElementType1(list); // Does not throw an exception.
showElementType2(list); // Throws an exception.
}
private static void showElementType1(IEnumerable<int> collection)
{
var enumerator = collection.GetEnumerator();
enumerator.MoveNext();
var type = enumerator.Current.GetType(); // No exception thrown here.
Console.WriteLine(type);
}
private static void showElementType2(IEnumerable collection)
{
var enumerator = collection.GetEnumerator();
enumerator.MoveNext();
var type = enumerator.Current.GetType(); // InvalidOperationException thrown here.
Console.WriteLine(type);
}
}
}