Why does IEnumerator<T> inherit from IDisposable while the non-generic IEnumerator does not?

爷,独闯天下 提交于 2019-11-27 06:47:08
Jon Skeet

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 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.

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.

Bevan

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.

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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!