Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

a concern about yield return and breaking from a foreach

Tags:

Is there a proper way to break from a foreach such that the IEnumerable<> knows that I'm done and it should clean up.

Consider the following code:

    private static IEnumerable<Person> getPeople()     {         using (SqlConnection sqlConnection = new SqlConnection("..."))         {             try             {                 sqlConnection.Open();                 using (SqlCommand sqlCommand = new SqlCommand("select id, firstName, lastName from people", sqlConnection))                 {                      using (SqlDataReader reader = sqlCommand.ExecuteReader())                     {                         while (reader.Read())                             yield return new Person(reader.GetGuid(0), reader.GetString(1), reader.GetString(2));                     }                 }             }             finally             {                 Console.WriteLine("finally disposing of the connection");                 if (sqlConnection.State == System.Data.ConnectionState.Open)                     sqlConnection.Close();             }         }     } 

If he consumer does not break from the foreach then everthing is fine and the reader will return false, the while loop willend and the function cleans up the database command and connection. But what happens if the caller breaks from the foreach before i'm finished?

like image 975
Ralph Shillington Avatar asked Sep 10 '09 15:09

Ralph Shillington


People also ask

What does yield break mean?

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. For example, if you define a function as an iterator, the body of the function may look like this: for (int i = 0; i < 5; i++) { yield return i; } Console.

Does yield break a loop?

In a normal (non-iterating) method you would use the return keyword. But you can't use return in an iterator, you have to use yield break . In other words, yield break for an iterator is the same as return for a standard method. Whereas, the break statement just terminates the closest loop.

Does yield break return null?

"yield break" breaks the Coroutine (it's similar as "return"). "yield return null" means that Unity will wait the next frame to finish the current scope. "yield return new" is similar to "yield return null" but this is used to call another coroutine.

What is the difference between yield and return in C#?

The only difference between yield and return is whenever yield statement is encountered in a function, the execution of function is suspended and a value is send back to the caller but because of yield whenever the function is called again, the execution of function begin where it left off previously.


2 Answers

Excellent question. You do not need to worry about this; the compiler takes care of it for you. Basically, what we do is we put the cleanup code for the finally blocks into a special cleanup method on the generated iterator. When control leaves the caller's foreach block, the compiler generates code which calls the cleanup code on the iterator.

A simplified example:

static IEnumerable<int> GetInts() {     try { yield return 1; yield return 2;}      finally { Cleanup(); } } 

Your question is basically "Is Cleanup() called in this scenario?"

foreach(int i in GetInts()) { break; } 

Yes. The iterator block is generated as a class with a Dispose method that calls Cleanup, and then the foreach loop is generated as something similar to:

{   IEnumerator<int> enumtor = GetInts().GetEnumerator();   try   {     while(enumtor.MoveNext())     {       i = enumtor.Current;       break;     }   }   finally   {     enumtor.Dispose();   } } 

So when the break happens, the finally takes over and the disposer is called.

See my recent series of articles if you want more information about some of the weird corner cases we considered in the design of this feature.

http://blogs.msdn.com/ericlippert/archive/tags/Iterators/default.aspx

like image 75
Eric Lippert Avatar answered Oct 23 '22 18:10

Eric Lippert


Let's see if I get your question.

foreach(Person p in getPeople()) {     // break here } 

because of the foreach keyword, the Enumerator is properly disposed. During the disposal of Enumerator, the execution of getPeople() is terminated. So the connection is properly cleaned up.

like image 28
Canton Avatar answered Oct 23 '22 18:10

Canton