foreach uses the interfaces in many cases. You need the interfaces if you want to implement a sequence which foreach can then use. (Iterator blocks usually make this implementation task very simple though.)
However, just occasionally it can be useful to use the iterators directly. A good example is when trying to "pair up" two different sequences. For example, suppose you receive two sequences - one of names, one of ages, and you want to print the two together. You might write:
static void PrintNamesAndAges(IEnumerable names, IEnumerable ages)
{
using (IEnumerator 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");
}
}
}
Likewise it can be useful to use the iterator if you want to treat (say) the first item differently to the rest:
public T Max(IEnumerable items)
{
Comparer comparer = Comparer.Default;
using (IEnumerator 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;
}
}
Now, if you're interested in the difference between IEnumerator and IEnumerable, you might want to think of it in database terms: think of IEnumerable as a table, and IEnumerator as a cursor. You can ask a table to give you a new cursor, and you can have multiple cursors over the same table at the same time.
It can take a while to really grok this difference, but just remembering that a list (or array, or whatever) doesn't have any concept of "where you are in the list" but an iterator over that list/array/whatever does have that bit of state is helpful.