Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

'yield' enumerations that don't get 'finished' by caller - what happens

suppose I have

IEnumerable<string> Foo() {      try      {           /// open a network connection, start reading packets          while(moredata)          {             yield return packet;          }      }      finally       {         // close connection        } } 

(Or maybe I did a 'using' - same thing). What happens if my caller goes

var packet = Foo().First(); 

I am just left with a leaked connection. When does the finally get invoked? Or does the right thing always happen by magic

edit with answer and thoughts

My sample and other 'normal' (foreach, ..) calling patterns will work nicely because they dispose of the IEnumerable (actually the IEnumerator returned by GetEnumerator). I must therefore have a caller somewhere thats doing something funky (explicitly getting an enumerator and not disposing it or the like). I will have them shot

the bad code

I found a caller doing

IEnumerator<T> enumerator = foo().GetEnumerator(); 

changed to

using(IEnumerator<T> enumerator = foo().GetEnumerator()) 
like image 421
pm100 Avatar asked Jun 09 '16 20:06

pm100


People also ask

Does yield break return?

It specifies that an iterator has come to an end. You can think of yield break as a return statement which does not return a value.

Can enumerations be incremented?

Incrementing an enumeration requires a cast to convert the integer result of addition back to the enumeration type, as in: d = day(d + 1); This seems to leave you with two less-than-perfect alternatives for writing loops that iterate over the day values.

What is the point of enumeration?

Enumerations make for clearer and more readable code, particularly when meaningful names are used. The benefits of using enumerations include: Reduces errors caused by transposing or mistyping numbers. Makes it easy to change values in the future.

Can enumerations have methods?

The enum class body can include methods and other fields. The compiler automatically adds some special methods when it creates an enum. For example, they have a static values method that returns an array containing all of the values of the enum in the order they are declared.


2 Answers

I am just left with a leaked connection.

No, you're not.

When does the finally get invoked?

When the IEnumerator<T> is disposed, which First is going to do after getting the first item of the sequence (just like everyone should be doing when they use an IEnumerator<T>).

Now if someone wrote:

//note no `using` block on `iterator` var iterator = Foo().GetEnumerator(); iterator.MoveNext(); var first = iterator.Current; //note no disposal of iterator 

then they would leak the resource, but there the bug is in the caller code, not the iterator block.

like image 114
Servy Avatar answered Sep 20 '22 15:09

Servy


You would not end up with leaked connection. Iterator objects produced by yield return are IDisposable, and LINQ functions are careful to ensure proper disposal.

For example, First() is implemented as follows:

public static TSource First<TSource>(this IEnumerable<TSource> source) {     if (source == null) throw Error.ArgumentNull("source");     IList<TSource> list = source as IList<TSource>;     if (list != null) {         if (list.Count > 0) return list[0];     }     else {         using (IEnumerator<TSource> e = source.GetEnumerator()) {             if (e.MoveNext()) return e.Current;         }     }     throw Error.NoElements(); } 

Note how the result of source.GetEnumerator() is wrapped in using. This ensures the call to Dispose, which in turn ensures the call of your code in the finally block.

Same goes for iterations by foreach loop: the code ensures disposal of the enumerator regardless of whether the enumeration completes or not.

The only case when you may end up with leaked connection is when you call GetEnumerator yourself, and fail to properly dispose of it. However, this is a mistake in the code using IEnumerable, not in the IEnumerable itself.

like image 41
Sergey Kalinichenko Avatar answered Sep 21 '22 15:09

Sergey Kalinichenko