代码之家  ›  专栏  ›  技术社区  ›  Joel Gauvreau

使用yield迭代DataReader可能无法关闭连接?

  •  15
  • Joel Gauvreau  · 技术社区  · 16 年前

    下面是一个使用yield关键字从数据库中检索数据的示例代码,我在搜索时在一些地方找到了该关键字:

    public IEnumerable<object> ExecuteSelect(string commandText)
    {
        using (IDbConnection connection = CreateConnection())
        {
            using (IDbCommand cmd = CreateCommand(commandText, connection))
            {
                 connection.Open();
                 using (IDbDataReader reader = cmd.ExecuteReader())
                 {
                    while(reader.Read())
                    {
                        yield return reader["SomeField"];
                    }
                 }
                 connection.Close();
            }
        }
    }
    

    我是否正确地认为,在这个示例代码中,如果我们不迭代整个数据阅读器,连接就不会关闭?

    下面是一个例子,如果我正确理解yield,它将不会关闭连接。

    foreach(object obj in ExecuteSelect(commandText))
    {
      break;
    }
    

    对于一个可能不是灾难性的DB连接,我想GC最终会清除它,但是如果它不是一个连接,而是一个更关键的资源呢?

    4 回复  |  直到 16 年前
        1
  •  11
  •   Douglas Leeder    16 年前

    编译器合成的迭代器实现IDisposable,当foreach循环退出时foreach调用该迭代器。

    迭代器的dispose()方法将在早期退出时清除using语句。

    只要在foreach循环中使用迭代器,使用()块,或者以其他方式调用dispose()方法,迭代器就会被清除。

        2
  •  2
  •   aku    16 年前

    连接将自动关闭,因为您在“使用”块内使用它。

        3
  •  2
  •   Joel Gauvreau    16 年前

    从我尝试过的简单测试来看,AKU是正确的,一旦foreach块退出,就会调用dispose。

    @大卫:但是调用堆栈在两次调用之间保持不变,所以连接不会被关闭,因为在下一次调用时,我们将在yield之后返回到下一条指令,即while块。

    我的理解是,当迭代器被释放时,连接也将被释放。我还认为不需要connection.close,因为当由于using子句而释放对象时,它会得到处理。

    这是一个简单的程序,我试着测试它的行为…

    class Program
    {
        static void Main(string[] args)
        {
            foreach (int v in getValues())
            {
                Console.WriteLine(v);
            }
            Console.ReadKey();
    
            foreach (int v in getValues())
            {
                Console.WriteLine(v);
                break;
            }
            Console.ReadKey();
        }
    
        public static IEnumerable<int> getValues()
        {
            using (TestDisposable t = new TestDisposable())
            {
                for(int i = 0; i<10; i++)
                    yield return t.GetValue();
            }
        }
    }
    
    public class TestDisposable : IDisposable
    {
        private int value;
    
        public void Dispose()
        {
            Console.WriteLine("Disposed");
        }
    
        public int GetValue()
        {
            value += 1;
            return value;
        }
    }
    
        4
  •  0
  •   David Schmitt    16 年前

    从判断 this technical explanation ,您的代码将无法按预期工作,但会在第二个项目上中止,因为返回第一个项目时连接已关闭。

    @乔尔:是的,我应该继续读下去。 Part 3 本系列中的说明编译器为finally块添加了特殊处理,以便仅在 真实的 结束。