Hi,
I was wondering why the GetEnumerator() method was factored out of IEnumerator and placed in IEnumerable. It seems to me that it would make more sense to keep all of the enumerator methods in IEnumerator.
Thanks,
Scott
Hi,
I was wondering why the GetEnumerator() method was factored out of IEnumerator and placed in IEnumerable. It seems to me that it would make more sense to keep all of the enumerator methods in IEnumerator.
Thanks,
Scott
IEnumerable
implies that the object is a collection or source of data which can be iterated over in a linear fashion. IEnumerator
is the interface for the actual implementation which performs the iteration.
Because "IEnumerable" says "come, enumerate me" (and then you say- how, give me the Enumerator), however "IEnumerator" says "I can enumerate your collection!" and you already have it, you don't need to get any more.
Because often the thing doing the enumerating is only tangentially (if at all) related to the thing being enumerated. If IEnumerable and IEnumerator were the same interface, they couldn't be shared, or you'd need to have some untyped argument on GetEnumerator that allowed you to pass in the object to be enumerated. Splitting them allows the enumeration infrastructure to be potentially shared across different types.
Ask yourself "imagine if this were true".
If all the enumeration methods were on a single interface, how could two callers enumerate the same list at the same time?
There are two interfaces because one says, "You can enumerate me," while the other says, "here's an object that keeps track of a given enumeration task."
The IEnumerable
interface is a factory that creates as many IEnumerator
objects as you want. How and when those enumerators get used is up to the consumer.
You got very good answers here. Just some emphasis. An enumerator keeps state, it keeps track of the current object in the collection being enumerated. Available through IEnumerator.Current. And it knows how to change that state, IEnumerator.MoveNext(). Keeping state requires a separate object that stores the state. That state can't be stored inside the collection object easily because there's only one collection object but there can be more than one enumerator.
I used the phrase "easily" because it is actually possible for a collection to keep track of its enumerators. After all, it was a call to the collection class' GetEnumerator() method that returned the iterator. There's one collection class in the .NET framework that does this, Microsoft.VisualBasic.Collection. It needed to implement a VB6 contract for its Collection class and that contract specifies that changing a collection while it is being enumerated is legal. Which means that when the Collection is modified, it needs to some reasonable with all of the iterator objects that were created.
They came up with a pretty good trick, WeakReference might have been inspired by this. Have a look-see at the code shown by Reflector. Inspiring stuff. Dig some more and find "version" in the collection classes. Awesome trick.
Here's my two cents concerning the difference in IEnumerator
and IEnumerable
.
Imagine a child who is learning how to read. While reading in some book, the child is using one finger as a guide, to keep track how far it has got with reading the current sentence.
Using that analogy, the sentence would be an object implementing the IEnumerable
interface, e.g. IEnumerable<Word>
. The child's finger is the iterator / an object implementing the IEnumerator
interface, let's say IEnumerator<Word>
.
Generally speaking, an iterable object (IEnumerable
) is some collection of elements, while an iterator (IEnumerator
) is like a pointer to the current element in such a collection.
From that perspective, it seldom makes sense to implement both interfaces in the same class.
I recently came across one case though where I chose to do exactly that. What I wanted was a generator for the Fibonacci series (1, 1, 2, 3, 5, 8, 13, ...). Basically, if the iterator is smart enough, it doesn't actually require an underlying collection but can generate the numbers "on the fly", so I chose to implement an object FibonacciSeries
implementing both IEnumerable
and IEnumerator
at the same time.
After reading Bart De Smet's post on minlinq I'm no longer convienced that splitting the two interfaces is strictly required.
For instance - In the above link, IEnumerable/IEnumerator is collaped to a single method Interface
Func<Func<Option<T>>>
and some basic implementations
public static class FEnumerable
{
public static Func<Func<Option<T>>> Empty<T>()
{
return () => () => new Option<T>.None();
}
public static Func<Func<Option<T>>> Return<T>(T value)
{
return () =>
{
int i = 0;
return () =>
i++ == 0
? (Option<T>)new Option<T>.Some(value)
: (Option<T>)new Option<T>.None();
};
}
...
}
public static Func<Func<Option<T>>> Where<T>(this Func<Func<Option<T>>> source, Func<T, bool> filter)
{
return source.Bind(t => filter(t) ? FEnumerable.Return(t) : FEnumerable.Empty<T>());
}