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?
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.
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.
"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.
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.
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
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With