Enumerator disposal when not using using, foreach or manually calling Dispose()
Asked Answered
D

3

12

I'm using yield return to iterate over an SqlDataReader's records:

IEnumerable<Reading> GetReadings() {
    using (var connection = new SqlConnection(_connectionString))
    {
        using (var command = new SqlCommand(_query, connection))
        {
            connection.Open();
            using (var reader = command.ExecuteReader())
            {
                while (reader.Read())
                {
                    yield return new Reading
                    {
                        End = reader.GetDateTime(0),
                        Value = reader.GetDouble(1)
                    };
                }
            }
            connection.Close();
        }
    }
}

I'm then using an adapted version of this accepted answer to "zip" many iterators together:

   var enumerators = data.Select(d => new
   {
       d.Key,
       Enumerator = d.Value.GetEnumerator()
   }).ToList();

   while (true)
   {
       foreach (var item in enumerators)
       {
           if (!item.Enumerator.MoveNext())
           {
               yield break;
           }

           /*snip*/
       }

      /*snip*/
   }

In the method above, the enumerator's Dispose() is not explicitly called, and because they are not used within a using or foreach statement, would the underlying iterator remain in an open state? In my case with an open SqlConnection.

Should I be calling Dispose() on the enumerators to make sure the whole downstream chain is closed?

Douma answered 24/1, 2014 at 16:13 Comment(2)
Why would you not use the using syntax? It gets called automatically when the block context ends, which includes yield and return.Ricardo
Having a variable number of enumerators in the zipmany style method makes it difficult to wrap each with using.Douma
S
29

When enumerating over this iterator, if the enumerator's Dispose() is not explicitly called, and not used within a using statement, would the underlying iterator remain in an open state?

Let me re-phrase that question into a form that is easier to answer.

When using foreach to enumerate via an iterator block that contains a using statement, are the resources disposed of when control leaves the loop?

Yes.

What mechanisms ensure this?

These three:

  • A using statement is just a convenient way to write a try-finally where the finally disposes of the resource.

  • The foreach loop is also a convenient syntax for try-finally, and again, the finally calls Dispose on the enumerator when control leaves the loop.

  • The enumerator produced by an iterator block implements IDisposable. Calling Dispose() on it ensures that all the finally blocks in the iterator block are executed, including finally blocks that come from using statements.

If I avoid the foreach loop, call GetEnumerator myself, and don't call Dispose on the enumerator, do I have a guarantee that the finally blocks of the enumerator will run?

Nope. Always dispose your enumerators. They implement IDisposable for a reason.

Is that now clear?

If this subject interests you then you should read my long series on design characteristics of iterator blocks in C#.

http://blogs.msdn.com/b/ericlippert/archive/tags/iterators/

Safety answered 24/1, 2014 at 16:24 Comment(3)
What about item which is Enumerator itself?Homozygous
@HamletHakobyan: An IEnumerator<T> implements IDisposable. If you have an IDisposable and you never call Dispose() then the disposal code doesn't run.Safety
Yes, it is obviously. My comment depend on OP question and I see misleading between question and answer.Homozygous
D
0

You call Dispose to deallocate resources allocated by the connection and call Close to just close the connection. AfterClose call the connection may be pushed in connection pool, if runtime that found suitable, in case of Dispose, instead, it will be scheduled for destroy .

So all depends on what you mean saying "valid" state.

If it's enclosed in usingdirrective, which is nothing else then try/finally, you have a guarantee that even if any exception happen in iteration the connection will be closed and its resources will be destroyed. In other cases you have to handle all it by yourself.

Demisec answered 24/1, 2014 at 16:20 Comment(0)
P
-1

Just to add some nuance to this:

Array-enumerators are NOT generic and are not disposable

Collection-enumerators ARE generic of and are disposable but only if T itself is a reference-type ... not a value-type

So if you use GetEnumerator(), be sure to use

IEnumerator arrayEnumerator = arrayToEnumerate.GetEnumerator();
IEnumerator<T> collectionEnumerator = collectionToEnumerate.GetEnumerator();

then later in your code

collectionEnumerator.Dispose(); // compiles if T is reference-type but NOT value-type

Examples:

object[] theObjectArray = [...]
IEnumerator arrayEnumerator = theObjectArray.GetEnumerator(); // does not return 
IEnumerator<object> and is not disposable
arrayEnumerator.Dispose(); // won't compile

List<double> theListOfDoubles = [...]
IEnumerator<someObject> doubleEnumerator = doubleEnumerator.GetEnumerator();
doubleEnumerator.Dispose(); // won't compile

List<someObject> theListOfObjects = [...]
IEnumerator<someObject> objectEnumerator = theListOfObjects.GetEnumerator(); // disposable
objectEnumerator.Dispose(); // compiles & runs
Puparium answered 24/1, 2014 at 16:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.