关于.net 2.0:使用yield遍历数据读取器可能不会关闭连接吗?

关于.net 2.0:使用yield遍历数据读取器可能不会关闭连接吗?

Using yield to iterate over a datareader might not close the connection?

这是一个示例代码,用于使用谷歌搜索时在几个地方找到的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块添加了特殊处理,以便仅在真实端触发。


推荐阅读