【问题标题】:a concern about yield return and breaking from a foreach对收益回报和脱离 foreach 的担忧
【发布时间】:2010-11-27 05:06:21
【问题描述】:

是否有适当的方法从 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 中中断,那么一切都很好,阅读器将返回 false,while 循环将结束并且函数清理数据库命令和连接。但是如果调用者在我完成之前从 foreach 中中断会发生什么?

【问题讨论】:

标签: c# .net


【解决方案1】:

你可以使用语句

yield break;

要早点跳出产量循环,但你的代码揭示了我认为的误解...... 当你使用“using”语句时,

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

你在编译的 IL 代码中自动得到一个 try finally 块,finnaly 块将调用 Dispose,在 Dispose 代码中连接将被关闭......

【讨论】:

    【解决方案2】:

    让我们看看我是否明白你的问题。

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

    由于使用了 foreach 关键字,Enumerator 被正确处理。在 Enumerator 的处理过程中,getPeople() 的执行被终止。所以连接被正确清理了。

    【讨论】:

      【解决方案3】:

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

      一个简化的例子:

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

      您的问题基本上是“在这种情况下是否调用了 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();
        }
      }
      

      所以当中断发生时,finally 接管并调用 disposer。

      如果您想了解有关我们在设计此功能时考虑的一些奇怪的极端情况的更多信息,请参阅我最近的系列文章。

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

      【讨论】:

        猜你喜欢
        • 2012-08-02
        • 2011-05-27
        • 2014-11-01
        • 1970-01-01
        • 1970-01-01
        • 2012-01-14
        • 1970-01-01
        • 2012-12-25
        • 2017-12-09
        相关资源
        最近更新 更多