Why does IEnumerator<T> inherit from IDisposable while the non-generic IEnumerator does not?
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.
Basically it was an oversight. In C# 1.0, foreach
never called Dispose
1. With C# 1.2 (introduced in VS2003 - there's no 1.1, bizarrely) foreach
began to check in the finally
block whether or not the iterator implemented IDisposable
- they had to do it that way, because retrospectively making IEnumerator
extend IDisposable
would have broken everyone's implementation of IEnumerator
. If they'd worked out that it's useful for foreach
to dispose of iterators in the first place, I'm sure IEnumerator
would have extended IDisposable
.
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 an IEnumerator<T>
it can emit an unconditional call to Dispose
.
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 generate Dispose
implementations which make sure that any finally
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 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 unmanaged 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.