关于收益率回报和对foreach的打破

有没有一种正确的方法来打破foreach,以便IEnumerable 知道我已经完成并且它应该清理。

请考虑以下代码:

private static IEnumerable 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(); } } } 

如果他的消费者没有脱离foreach那么everthing就好了,读者将返回false,while循环willend并且该函数清除数据库命令和连接。 但是如果呼叫者在我完成之前从foreach中断了会发生什么呢?

好问题。 你不必担心这个; 编译器会为你处理它。 基本上,我们所做的是将finally块的清理代码放在生成的迭代器上的特殊清理方法中。 当控制离开调用者的foreach块时,编译器会生成调用迭代器上的清理代码的代码。

一个简化的例子:

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

你的问题基本上是“在这种情况下调用了清理()吗?”

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

是。 迭代器块是作为一个类生成的,带有调用Cleanup的Dispose方法,然后生成foreach循环,类似于:

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

因此,当突破发生时,最终接管并调用处理器。

如果您想了解我们在设计此function时考虑的一些奇怪角落案例的更多信息,请参阅我最近的一系列文章。

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

让我们看看我是否得到你的问题。

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

由于foreach关键字,Enumerator已正确处理。 在处理Enumerator期间,getPeople()的执行被终止。 因此,连接已正确清理。

您可以使用该声明

 yield break; 

尽早摆脱收益循环,但你的代码揭示了我认为的误解……当你使用“使用”声明时,

 using (SqlConnection sqlConnection = new SqlConnection("...")) { // other stuff } 

你自动在编译的IL代码中尝试finally块,finnaly块将调用Dispose,并在Dispose代码中关闭连接…