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

时间:2009-09-10 15:08:36

标签: c# .net

是否有正确的方法可以从foreach中断,以便IEnumerable<>知道我已经完成了它应该清理。

请考虑以下代码:

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

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

3 个答案:

答案 0 :(得分:33)

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

简化示例:

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

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

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

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

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

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

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

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

答案 1 :(得分:2)

您可以使用声明

yield break;

尽早摆脱收益率循环,但你的代码揭示了我认为的误解...... 当您使用“using”语句时,

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

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

答案 2 :(得分:2)

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

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

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