这是一个示例代码,用于使用谷歌搜索时在几个地方找到的yield关键字从数据库检索数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| 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,这是一个不会关闭连接的示例。
1 2 3 4
| foreach(object obj in ExecuteSelect(commandText))
{
break;
} |
对于可能不会造成灾难性的数据库连接,我想GC最终会清理它,但是如果不是连接而是更关键的资源怎么办?
编译器综合的迭代器实现IDisposable,当退出foreach循环时,foreach会调用该迭代器。
迭代器的Dispose()方法将在提早退出时清除using语句。
只要您在foreach循环,using()块中使用迭代器,或以其他方式调用Dispose()方法,就会进行迭代器的清理。
从我尝试过的简单测试来看,aku是正确的,只要foreach块退出,就立即调用配置。
@David:但是在调用之间保持调用堆栈,因此连接不会关闭,因为在下一个调用中,我们将在yield之后的下一条指令返回while语句,这是while块。
我的理解是,当处置迭代器时,连接也将随之处置。我也认为不需要Connection.Close,因为由于using子句,它将在处理对象时照顾到它。
这是我尝试测试行为的简单程序...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| 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;
}
} |
由于您在"使用"块中使用连接,连接将自动关闭。
从此技术说明来看,您的代码将无法按预期运行,但会在第二项上中止,因为返回第一项时连接已关闭。
@Joel Gauvreau:是的,我应该继续读下去。本系列的第3部分解释了编译器为final块添加了特殊处理,以便仅在真实端触发。