9

So I've notice that this code works:

class Program
{
    public static void Main()
    {
        Int32[ ]numbers = {1,2,3,4,5};

        using (var enumerator = Data().GetEnumerator())
        {

        }
    }

    public static IEnumerable<String> Data()
    {
        yield return "Something";
    }
}

In particular, I'm curious about the using block, since:

Int32[] numbers = { 1, 2, 3, 4, 5, 6 };

using (var enumerator = numbers.GetEnumerator())
{

}

fails with a compiler error. Obviously, the class that yield return returns is IDisposable while a regular array enumerator is not. So now I'm curious: what exactly does yield return create?

sircodesalot
  • 11,231
  • 8
  • 50
  • 83
  • 2
    possible duplicate of [Why IEnumerator of T inherts from IDisposable, but non-generic IEnumerator does NOT?](http://stackoverflow.com/questions/232558/why-ienumerator-of-t-inherts-from-idisposable-but-non-generic-ienumerator-does) – Tim Schmelter Mar 11 '13 at 15:06

1 Answers1

11

IEnumerator<T> implements IDisposable, as you can see in the Object Browser or in MSDN.

The non-generic IEnumerator does not.

The base Array class implements IEnumerable but not IEnumerable<T>. (since Array is not generic)
Concrete array types do implement IEnumerable<T>, but they implement GetEnumerator() explicitly (I'm not sure why).
Therefore, the GetEnumerator() visible on any array type returns IEnumerator.

The generic IEnumerable<T> implementation returns a System.SZArrayHelper.SZGenericArrayEnumerator<T>.

The source code for this class (in Array.cs) has the following comment which partially explains this (remember, all support for generic arrays dates back to a time when IEnumerable<T> was not contraviant)

//--------------------------------------------------------------------------------------- 
// ! READ THIS BEFORE YOU WORK ON THIS CLASS. 
//
// The methods on this class must be written VERY carefully to avoid introducing security holes. 
// That's because they are invoked with special "this"! The "this" object
// for all of these methods are not SZArrayHelper objects. Rather, they are of type U[]
// where U[] is castable to T[]. No actual SZArrayHelper object is ever instantiated. Thus, you will
// see a lot of expressions that cast "this" "T[]". 
//
// This class is needed to allow an SZ array of type T[] to expose IList<T>, 
// IList<T.BaseType>, etc., etc. all the way up to IList<Object>. When the following call is 
// made:
// 
//   ((IList<T>) (new U[n])).SomeIListMethod()
//
// the interface stub dispatcher treats this as a special case, loads up SZArrayHelper,
// finds the corresponding generic method (matched simply by method name), instantiates 
// it for type <T> and executes it.
// 
// The "T" will reflect the interface used to invoke the method. The actual runtime "this" will be 
// array that is castable to "T[]" (i.e. for primitivs and valuetypes, it will be exactly
// "T[]" - for orefs, it may be a "U[]" where U derives from T.) 
//---------------------------------------------------------------------------------------
SLaks
  • 868,454
  • 176
  • 1,908
  • 1,964
  • While we are at it, I haven't seen any code that uses a `using` block as shown by OP. Is this necessary, considering that it is mere looping on the items than using an OS resource such as connection, file, bitmap etc. – shahkalpesh Mar 11 '13 at 15:21
  • 1
    @shahkalpesh: `foreach` will call `Dispose()` in a `finally` block if the enumerator implements `IDisposable`. If you call `GetEnumerator()` yourself, it's your responsibility to dispose it. `IDisposable` is not only for unmanaged resources; it's for anything that needs deterministic cleanup. Also, what about `Directory.EnumerateFiles()`? – SLaks Mar 11 '13 at 15:24
  • The reason that array types implement `IEnumerable` implicitly and `IEnumerable` explicitly is backwards compatibility. There was a non-generic `GetEnumerator` instance method, and they couldn't take it away without it being a (more significant) breaking change. The majority of the other collections used were all added in or after .NET 2.0, so `IEnumerable` existed when they were first created, and that could be the implicitly implemented interface. Any collection existing before then most likely doesn't implement `IEnumerable` implicitly (then again, most can't at all...) – Servy Mar 11 '13 at 15:26
  • @Servy: Are you sure that changing `GetEnumerator()` to return a type that implements its previous return type is a breaking change? I suspect that this is mostly due to the variance issue. (arrays implement more than one `IEnumerable`) – SLaks Mar 11 '13 at 15:30
  • @SLaks: Thanks for your reply. I think you'll agree that `Dispose` is needed to do cleanup of OS resource used (which needs deterministic cleanup than to wait for GC to do it). In that sense, `foreach` is a better way to deal with `Directory.EnumerateFiles` than using `for(;;)` or `GetNumerator`. Is that fair to assume? – shahkalpesh Mar 11 '13 at 15:36
  • @shahkalpesh: No. `Dispose()` can be needed for other things too (eg, to release a read lock, or for reference counting). And, `foreach()` is not the only way to consume an `IEnumerator` (many LINQ methods don't use it, for example). As long as you make sure to dispose it (typically using `using`), you're fine. – SLaks Mar 11 '13 at 15:47