Just wondering why Enumerable.Range implements IDisposable.

I understand why IEnumerator<T> does, but IEnumerable<T> doesn't require it.

(I discovered this while playing with my .Memoise() implementation, which has statement like

if (enumerable is IDisposable)

in its "source finished" method that I had placed a breakpoint on out of curiousity, and was triggered by a test.)


Enumerable.Range uses yield return in its method body. The yield return statement produces an anonymous type that implements IDisposable, under the magic of the compiler, like this:

static IEnumerable<int> GetNumbers()
    for (int i = 1; i < 10; i += 2)
        yield return i;

After being compiled, there is an anonymous nested class like this:

private sealed class <GetNumbers>d__0 
   : IEnumerable<int>, IEnumerable, IEnumerator<int>, IEnumerator, IDisposable
    //the implementation
    //note the interface is implemented explicitly
    void IDisposable.Dispose() { }

so the result is a IDisposable. In this example, the Dispose method leaves empty. I think the reason is that there is nothing need to be disposed. If you yield return a type that contains unmanaged resources, you may get a different compiling result. (NOT SURE about it)

  • The compiler implements both IEnumerable and IEnumerator with the same class? Interesting. I'll have to think about that works. – Fowl Jul 4 '12 at 4:21
  • Oh, I see it now. It uses its state machine to detect if GetEnumerator has been called multiple times and returns itself if it hasn't. – Fowl Jul 4 '12 at 4:33
  • That makes sense, but having a factory method return a type which implements IDisposable when the return type of the factory does not would seem to be an anti-pattern. Compiler-generated iterators produced via yield return would appear to be types where abandonment without disposal would in fact be harmless provided GetEnumerator() has never been called, but once GetEnumerator() has been called, disposal is generally necessary. Incidentally, calling Dispose on an iterator after GetEnumerable() has been called at least once will invalidate the first enumerator only. – supercat Jul 5 '12 at 18:08
  • Is calling Dispose on a iterator defined to invalidate (or not) all of its enumerators though? Either way having different behaviour for the first GetEnumerable call is unexpected. Premature optimisation by the compiler? – Fowl Jul 6 '12 at 0:23
  • (To clarify @supercat, calling Dispose on a compiler generated IEnumerable implementation is never necessary, unless you're mishandling the iterator.) – Fowl Jul 6 '12 at 1:16

Your Answer

