IEnumerable, IEnumerator против foreach, когда использовать что
Я проходил через IEnumerable и IEnumerator, но не смог получить однозначное представление... если у нас есть foreach, тогда зачем нам эти два интерфейса? Есть ли сценарий, где мы должны использовать интерфейсы. Если да, то кто-нибудь может объяснить с примером. Любые предложения и замечания приветствуются. Благодарю.
3 ответа
foreach
использует интерфейсы во многих случаях. Вам нужны интерфейсы, если вы хотите реализовать последовательность, которая foreach
затем можно использовать. (Блоки итераторов обычно делают эту задачу очень простой.)
Однако иногда бывает полезно использовать итераторы напрямую. Хорошим примером является попытка "спарить" две разные последовательности. Например, предположим, что вы получили две последовательности - одну из имен, одну из возрастов и хотите напечатать их вместе. Вы могли бы написать:
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>
как курсор. Вы можете попросить таблицу дать вам новый курсор, и вы можете иметь несколько курсоров на одной и той же таблице одновременно.
Это может занять некоторое время, чтобы по-настоящему справиться с этой разницей, но просто помните, что список (или массив, или что-то еще) не имеет никакого понятия "где вы находитесь в списке", но итератор над этим списком / массивом / что бы он ни делал иметь этот кусок состояния полезно.
Что сказал Джон
IEnumerable
или жеIEnumerable<T>
: реализуя это, объект заявляет, что он может дать вам итератор, который вы можете использовать для обхода последовательности / коллекции / набораIEnumerator
или жеIEnumerator<T>
: если вы вызываете метод GetEnumerator, определенный в предыдущем интерфейсе, вы получаете объект итератора в качестве ссылки IEnumerator. Это позволяет вам вызывать MoveNext() и получать текущий объект.foreach
: является конструкцией / фасадом C# в том смысле, что вам не нужно знать, как она работает под капотом. Он внутренне получает итератор и вызывает нужные методы, чтобы сконцентрироваться на том, что вы хотите сделать с каждым элементом (содержимым блока foreach). В большинстве случаев вам просто понадобится foreach, если вы не реализуете свой собственный тип или пользовательскую итерацию, и в этом случае вам необходимо познакомиться с первыми двумя интерфейсами.
Имейте в виду, что вам не нужно реализовывать IEnumerator и его разновидности для использования foreach - IIRC, все, что ему нужно, - это метод GetEnumerator(), который возвращает объект, у которого есть метод MoveNext(), возвращающий bool, и свойство Current, возвращающее объект. Вам не нужно использовать IEnumerator и IEnumerable, хотя обычно это очень хорошая идея. Смотрите здесь для получения дополнительной информации.