Я проходил через IEnumerable и IEnumerator, но не мог четко разобраться. Если у нас есть foreach, то зачем нам нужны эти два интерфейса? Есть ли какой-либо сценарий, где мы должны использовать интерфейсы. Если да, то кто-нибудь может объяснить пример. Любые предложения и замечания приветствуются. Спасибо.
IEnumerable, IEnumerator vs foreach, когда использовать
Ответ 1
foreach
использует интерфейсы во многих случаях. Вам нужны интерфейсы, если вы хотите реализовать последовательность, которую затем может использовать foreach
. (Блоки Iterator обычно делают эту задачу реализации очень простой, хотя.)
Однако иногда бывает полезно использовать итераторы напрямую. Хорошим примером является попытка "соединить" две разные последовательности. Например, предположим, что вы получаете две последовательности - одно из имен, один из возрастов, и вы хотите напечатать их вместе. Вы можете написать:
static void PrintNamesAndAges(IEnumerable<string> names, IEnumerable<int> ages)
{
using (IEnumerator<int> ageIterator = ages.GetEnumerator())
{
foreach (string name in names)
{
if (!ageIterator.MoveNext())
{
throw new ArgumentException("Not enough ages");
}
Console.WriteLine("{0} is {1} years old", name, ageIterator.Current);
}
if (ageIterator.MoveNext())
{
throw new ArgumentException("Not enough names");
}
}
}
Также полезно использовать итератор, если вы хотите по-разному относиться к первому элементу:
public T Max<T>(IEnumerable<T> items)
{
Comparer<T> comparer = Comparer<T>.Default;
using (IEnumerator<T> iterator = items.GetEnumerator())
{
if (!iterator.MoveNext())
{
throw new InvalidOperationException("No elements");
}
T currentMax = iterator.Current;
// Now we've got an initial value, loop over the rest
while (iterator.MoveNext())
{
T candidate = iterator.Current;
if (comparer.Compare(candidate, currentMax) > 0)
{
currentMax = candidate;
}
}
return currentMax;
}
}
Теперь, если вас интересует разница между IEnumerator<T>
и IEnumerable<T>
, вы можете подумать об этом в терминах базы данных: подумайте о IEnumerable<T>
в качестве таблицы и IEnumerator<T>
в качестве курсора. Вы можете попросить таблицу дать вам новый курсор, и вы можете одновременно иметь несколько курсоров по одной и той же таблице.
Может потребоваться некоторое время, чтобы действительно понять это различие, но просто помню, что в списке (или массиве или что-то еще) нет понятия "где вы находитесь в списке", но итератор по этому списку/массиву/независимо от того, что этот бит состояния полезен.
Ответ 2
Что сказал Джон.
-
IEnumerable
илиIEnumerable<T>
: путем реализации этого объекта указано, что он может дать вам итератор, который вы можете использовать для перемещения по последовательности/коллекции/набору -
IEnumerator
илиIEnumerator<T>
: если вы вызываете метод GetEnumerator, определенный в предыдущем интерфейсе, вы получаете объект итератора как ссылку IEnumerator. Это позволяет вам вызвать MoveNext() и получить текущий объект. -
foreach
: это конструкция или фасад С# в том смысле, что вам не нужно знать, как это работает под капотом. Он внутренне получает итератор и называет правильные методы для вас, чтобы сосредоточиться на том, что вы хотите делать с каждым элементом (содержимое блока foreach). В большинстве случаев вам просто нужно использовать foreach, если вы не реализуете свой собственный тип или пользовательскую итерацию, и в этом случае вам нужно будет ознакомиться с первыми двумя интерфейсами.
Ответ 3
Имейте в виду, что вы не имеете для реализации IEnumerator и его переменных для использования foreach - IIRC, все, что ему нужно, это метод GetEnumerator(), который возвращает объект с MoveNext() метод, возвращающий bool, и текущее свойство, возвращающее объект. Вам не нужно использовать IEnumerator и IEnumerable, хотя, как правило, это очень хорошая идея. См. здесь для получения дополнительной информации.