不使用,foreach或手动调用Dispose()时的枚举器处理

时间:2014-01-24 16:13:43

标签: c# enumeration enumerator yield-return

我正在使用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()以确保整个下游链都已关闭吗?

3 个答案:

答案 0 :(得分:17)

  

枚举这个迭代器时,如果未显式调用枚举器的Dispose(),并且未在using语句中使用,那么底层迭代器是否会保持打开状态?

让我重新将这个问题改成一个更容易回答的形式。

  

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

  

有哪些机制可以确保这一点?

这三个:

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

  • foreach循环也是 try-finally的方便语法,同样,finally调用Dispose枚举器当控制离开循环时。

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

  

如果我避开foreach循环,请自行致电GetEnumerator,不要在调查器上调用Dispose,我是否有finally块的保证枚举器会运行吗?

不。 始终处置您的枚举器。他们出于某种原因实施IDisposable

现在清楚了吗?

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

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

答案 1 :(得分:0)

您致电Dispose取消分配连接分配的资源并致电Close以关闭连接。在Close调用之后,可以在连接池中推送连接,如果找到合适的运行时,在Dispose的情况下,它将被安排销毁。

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

如果它包含在using dirrective中,除了try/finally之外没有其他任何内容,您可以保证即使在迭代中发生任何异常,连接也将被关闭它的资源将被销毁。在其他情况下,你必须自己处理所有这些。

答案 2 :(得分:-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