I noticed that the generic IEnumerator<T>
inherits from IDisposable, but the non-generic interface IEnumerator does not. Why is it designed in this way?
Usually, we use foreach statement to go through a IEnumerator<T>
instance. The generated code of foreach actually has try-finally block that invokes Dispose() in finally.
IIRC The whole thing about having
IEnumerable<T>
andIEnumerable
is a result ofIEnumerable
predating .Net's template stuff. I suspect that your question is in the same way.Does IEnumerable` inherit IDisposing? According to the .NET reflector or MSDN. Are you sure you're not confusing it with IEnumerator? That uses IDisposing because it only for enumerating a collection and not meant for longevity.
A bit hard to be definitive on this, unless you manage to get a response from AndersH himself, or someone close to him.
However, my guess is that it relates to the "yield" keyword that was introduced in C# at the same time. If you look at the code generated by the compiler when "yield return x" is used, you'll see the method wrapped up in a helper class that implements IEnumerator; having IEnumerator descend from IDisposable ensures that it can clean up when enumeration is complete.
Basically it was an oversight. In C# 1.0,
foreach
never calledDispose
1. With C# 1.2 (introduced in VS2003 - there's no 1.1, bizarrely)foreach
began to check in thefinally
block whether or not the iterator implementedIDisposable
- they had to do it that way, because retrospectively makingIEnumerator
extendIDisposable
would have broken everyone's implementation ofIEnumerator
. If they'd worked out that it's useful forforeach
to dispose of iterators in the first place, I'm sureIEnumerator
would have extendedIDisposable
.When C# 2.0 and .NET 2.0 came out, however, they had a fresh opportunity - new interface, new inheritance. It makes much more sense to have the interface extend
IDisposable
so that you don't need an execution-time check in the finally block, and now the compiler knows that if the iterator is anIEnumerator<T>
it can emit an unconditional call toDispose
.EDIT: It's incredibly useful for
Dispose
to be called at the end of iteration (however it ends). It means the iterator can hold on to resources - which makes it feasible for it to, say, read a file line by line. Iterator blocks generatorDispose
implementations which make sure that anyfinally
blocks relevant to the "current point of execution" of the iterator are executed when it's disposed - so you can write normal code within the iterator and clean-up should happen appropriately.1 Looking back at the 1.0 spec, it was already specified. I haven't yet been able to verify this earlier statement that the 1.0 implementation didn't call
Dispose
.IEnumerable<T> doesn't inherit IDisposable. IEnumerator<T> does inherit IDisposable however, whereas the non-generic IEnumerator doesn't. Even when you use foreach for a non-generic IEnumerable (which returns IEnumerator), the compiler will still generate a check for IDisposable and call Dispose() if the enumerator implements the interface.
I guess the generic Enumerator<T> inherits from IDisposable so there doesn't need to be a runtime type-check—it can just go ahead and call Dispose() which should have better performance since it can be probably be optimized away if the enumerator has an empty Dispose() method.
I know this is an old discussion but I reasontly wrote a library where I used IEnumerable of T/IEnumerator of T where users of the library could implement custom iterators they should just implement IEnumerator of T.
I found it very strange that IEnumerator of T would inherit from IDisposable. We implement IDisposable if we want to free unmanged resources right? So it would only be relevant for enumerators that actually hold unmanaged resources - like an IO stream etc. Why not just let users implement both IEnumerator of T and IDisposable on their enumerator if it makes sense? In my book this violates the single responsibility principle - Why mix enumerator logic and disposing objects.