【问题标题】:Enumerator disposal when not using using, foreach or manually calling Dispose()不使用 using、foreach 或手动调用 Dispose() 时的枚举器处理
【发布时间】:2014-02-15 17:20:42
【问题描述】:

我正在使用yield return 迭代SqlDataReader 的记录:

IEnumerable<Reading> GetReadings() {
    using (var connection = new SqlConnection(_connectionString))
    {
        using (var command = new SqlCommand(_query, connection))
        {
            connection.Open();
            using (var reader = command.ExecuteReader())
            {
                while (reader.Read())
                {
                    yield return new Reading
                    {
                        End = reader.GetDateTime(0),
                        Value = reader.GetDouble(1)
                    };
                }
            }
            connection.Close();
        }
    }
}

然后我使用 this accepted answer 的改编版本将许多迭代器“压缩”在一起:

   var enumerators = data.Select(d => new
   {
       d.Key,
       Enumerator = d.Value.GetEnumerator()
   }).ToList();

   while (true)
   {
       foreach (var item in enumerators)
       {
           if (!item.Enumerator.MoveNext())
           {
               yield break;
           }

           /*snip*/
       }

      /*snip*/
   }

在上面的方法中,枚举器的Dispose() 没有被显式调用,并且因为它们没有在usingforeach 语句中使用,底层迭代器会保持打开状态吗?在我的情况下,打开SqlConnection

我是否应该在枚举器上调用Dispose() 以确保整个下游链都已关闭?

【问题讨论】:

  • 为什么不使用using 语法?它在块上下文结束时自动调用,包括yieldreturn
  • 在 zipmany 样式方法中具有可变数量的枚举器使得很难用using 包装每个枚举器。

标签: c# enumeration enumerator yield-return


【解决方案1】:

只是为了添加一些细微差别:

数组枚举器不是通用的,也不是一次性的

集合枚举器是通用的并且是一次性的,但前提是 T 本身是引用类型......而不是值类型

所以如果你使用GetEnumerator(),一定要使用

IEnumerator arrayEnumerator = arrayToEnumerate.GetEnumerator();
IEnumerator<T> collectionEnumerator = collectionToEnumerate.GetEnumerator();

然后在你的代码中

collectionEnumerator.Dispose(); // compiles if T is reference-type but NOT value-type

例子:

object[] theObjectArray = [...]
IEnumerator arrayEnumerator = theObjectArray.GetEnumerator(); // does not return 
IEnumerator<object> and is not disposable
arrayEnumerator.Dispose(); // won't compile

List<double> theListOfDoubles = [...]
IEnumerator<someObject> doubleEnumerator = doubleEnumerator.GetEnumerator();
doubleEnumerator.Dispose(); // won't compile

List<someObject> theListOfObjects = [...]
IEnumerator<someObject> objectEnumerator = theListOfObjects.GetEnumerator(); // disposable
objectEnumerator.Dispose(); // compiles & runs

【讨论】:

    【解决方案2】:

    您调用Dispose 来释放连接分配的资源,然后调用Close 来关闭连接。在Close 调用之后,连接可能会被推送到连接池中,如果运行时发现合适,则在Dispose 的情况下,它将被安排销毁。

    所以一切都取决于你所说的“有效”状态。

    如果它包含在usingdirrective 中,即try/finally,您可以保证即使迭代中发生任何异常,连接也会关闭并且它的资源将被关闭被摧毁。在其他情况下,您必须自己处理所有这些。

    【讨论】:

      【解决方案3】:

      在对这个迭代器进行枚举时,如果枚举器的Dispose() 没有被显式调用,也没有在using 语句中使用,那么底层迭代器会保持打开状态吗?

      让我把这个问题改成更容易回答的形式。

      当使用foreach 通过包含using 语句的迭代器块进行枚举时,资源是否在控制离开循环时被释放?

      是的。

      什么机制可以确保这一点?

      这三个:

      • using 语句只是编写try-finally 的便捷方式,finally 用于处理资源。

      • foreach 循环也是try-finally 的便捷语法,当控制离开循环时,finally 再次调用枚举器上的Dispose。 p>

      • 迭代器块产生的枚举器实现IDisposable。对其调用Dispose() 可确保迭代器块中的所有finally 块都被执行,包括来自using 语句的finally 块。

      如果我避免foreach 循环,我自己调用GetEnumerator,并且不对枚举器调用Dispose,我是否可以保证枚举器的finally 块会运行?

      不。 始终处置您的枚举器。他们实施IDisposable 是有原因的。

      现在清楚了吗?

      如果您对这个主题感兴趣,那么您应该阅读我关于 C# 中迭代​​器块的设计特征的长系列。

      http://blogs.msdn.com/b/ericlippert/archive/tags/iterators/

      【讨论】:

      • itemEnumerator 本身呢?
      • @HamletHakobyan:IEnumerator&lt;T&gt; 实现了IDisposable。如果你有一个IDisposable 并且你从不调用Dispose() 那么处理代码不会运行。
      • 是的,很明显。我的评论取决于 OP 问题,我认为问题和答案之间存在误导。
      猜你喜欢
      • 1970-01-01
      • 2020-03-06
      • 2017-09-10
      • 1970-01-01
      • 1970-01-01
      • 2012-03-04
      • 2012-04-30
      • 1970-01-01
      • 2012-08-07
      相关资源
      最近更新 更多