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,
foreachnever calledDispose1. With C# 1.2 (introduced in VS2003 – there’s no 1.1, bizarrely)foreachbegan to check in thefinallyblock whether or not the iterator implementedIDisposable– they had to do it that way, because retrospectively makingIEnumeratorextendIDisposablewould have broken everyone’s implementation ofIEnumerator. If they’d worked out that it’s useful forforeachto dispose of iterators in the first place, I’m sureIEnumeratorwould 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
IDisposableso 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
Disposeto 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 generateDisposeimplementations which make sure that anyfinallyblocks 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.