views:

1069

answers:

5

I noticed that generic IEnumerator(of T) inherits from IDisposable, but the non-generic interface IEnumerator does NOT. Why it is designed in this way?

Usually, we use foreach statement to go through a IEnumerator(of T) instance. The generated code of foreach actually has try-finally block that invokes Dispose() in finally.

Edit: My mind is a little mess today. I typed IEnumerable while thinking about IEnumerator. Have changed it.

A: 

IIRC The whole thing about having IEnumerable<T> and IEnumerable is a result of IEnumerable predating .Net's template stuff. I suspect that your question is in the same way.

BCS
A: 

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.

Slace
+4  A: 

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.

Mark Cidade
Better answer than mine :(
Slace
A: 

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.

Bevan
yield just makes the compiler generate code for a state machine which doesn't need any disposing beyond normal GC
Mark Cidade
@marxidad: Entirely incorrect. Consider what happens if a "using" statement occurs in an iterator block. See http://csharpindepth.com/Articles/Chapter6/IteratorBlockImplementation.aspx
Jon Skeet
@Jon: Not entirely incorrect. Although IDisposable isn't strictly necessary for cases where using *isn't* used, it is simpler to just make all new-style enumerators disposable and call Dispose() every time just in case.
Mark Cidade
I was about to reword my comment to make it a little bit softer than "entirel incorrect" but claiming that the compiler generates code which doesn't need any disposing is still inaccurate IMO. It *sometimes* does, but your blanket statement was incorrect.
Jon Skeet
s/just/sometimes/
Mark Cidade
+18  A: 

Basically it was an oversight. In C# 1.0, foreach never called Dispose. 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 generator 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.

Jon Skeet