使用yield遍历数据读取器可能不会关闭连接吗?
时间:2020-03-05 18:48:42 来源:igfitidea点击:
这是一个示例代码,用于使用谷歌搜索时在几个地方找到的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; }
对于可能不会造成灾难性的数据库连接,我想GC最终会清理它,但是如果它不是连接而是更关键的资源,该怎么办?
解决方案
回答
由于我们在"使用"块中使用连接,因此连接将自动关闭。
回答
从此技术说明来看,代码将无法按预期运行,但会在第二项上中止,因为返回第一项时连接已关闭。
@Joel Gauvreau:是的,我应该继续读下去。本系列的第3部分解释了编译器为final块添加了特殊处理,以便仅在实际末尾触发。
回答
从我尝试过的简单测试来看,aku是正确的,只要foreach块退出,就立即调用配置。
@David:但是在调用之间保持调用堆栈,因此连接不会关闭,因为在下一个调用中,我们将在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; } }
回答
编译器综合的迭代器实现IDisposable,它在退出foreach循环时会进行foreach调用。
迭代器的Dispose()方法将在提早退出时清除using语句。
只要我们在foreach循环中使用iterator,使用using()块,或者以其他方式调用Dispose()方法,就会对Iterator进行清理。