C#中使用双IDisposable的using语句

4
我有以下使用 using 的语句:
using (var reader = data.CreateCommand(sql).ExecuteDataReader())

在这种情况下,data是一个内部持有SqlConnection的对象。 CreateCommand(sql)函数返回SqlCommand,而ExecuteDataReader返回SqlDataReader。由于SqlCommandSqlDataReader都是IDisposable,所以它们都会被这个using语句释放吗?
目前我是这样做的:
using (var cmd = data.CreateCommand(sql))
using (var reader = cmd.ExecuteDataReader())

但我想知道是否可能像上述那样将它们组合起来?

3
根据您提供的信息,答案是您需要单独使用using语句来完成它。 - Matthew Watson
我需要提供更多信息吗?虽然我也意识到它不适用于单个using。不过如果有更多的using就好了,特别是很不错的。 - Jure
“data” 是你编写的用于访问数据库的自定义类吗?我的意思是你能修改它的代码吗? - S.Serpooshan
根据我的理解,释放 SqlConnection 将会释放所有使用它的资源,因此 SqlCommandSqlDataReader 也会随之被释放... 也就是说,我认为释放 SqlCommand 将会释放 SqlDataReader。所以你不必将 SqlReader 放在 using 语句中。 - Svek
@Svek,释放 SqlConnection 不会释放相关的 SqlCommand 或读取器! - S.Serpooshan
显示剩余3条评论
4个回答

4

"如果可以将它们合并" - 两个using都需要被处理,所以完全没有问题。

你可以通过提取为方法来将它们合并:

void RunWithReader(string sql, Action<SQLDataReader> action)
{
    using (var cmd = data.CreateCommand(sql))
    using (var reader = cmd.ExecuteDataReader())
        action(reader);
}

然后您就可以使用lambda。
RunWithReader("whatever command", reader =>
{
    ... // while(reader.Read() { ... } - this could also be extracted
});

我最终采用了这个解决方案。谢谢! - Jure

3

有趣的是,垃圾收集器在读取器被销毁后不会只是移除SqlCommand对象吗?编辑:不,很可能不会,因为 data 没有被销毁... - JHBonarius

2
你展示代码的方式中内部的IDisposable (IDbCommand) 没有被释放。
你有两个选择:
你可以把它放在一个using代码块里,像这样:
using (IDisposable cmd = data.CreateCommand(), reader = ((IDbCommand)cmd).ExecuteReader())
{
    // code here
}

但是这种方法有些繁琐。另一种选择是使用嵌套的using语句:

using (var cmd = data.CreateCommand())
{
    using (var reader = cmd.ExecuteReader())
    {
        // code here
    }
}

除此之外,您可以编写扩展方法来帮助(有点)简化它。

public static class Ex
{
    public static void Using<T, R>(this T obj, Func<T, R> create, Action<T, R> use) where R : IDisposable
    {
        using (var d = create(obj))
        {
            use(obj, d);
        }
    }
}

然后您可以这样做:
data.Using(d => d.CreateCommand(), (d, c) => c.Using(c2 => c2.ExecuteReader(), (c3, r) =>
{
    // code here
}));

但也许这并没有太大的改进。

-1

概述

您应该Dispose实现IDisposable接口的每个类。

然而,您的问题具体涉及到的情况所有资源都相关联,如果我的理解是正确的,垃圾回收器将自动为您处理这些资源的释放。


Finalize()方法(“析构函数”或“终结器”)

Finalize()方法确保在应用程序不再引用实例时处理资源。

Dispose()方法一起使用

Dispose()方法将“释放组件使用的所有资源”。这意味着从MSDN's ExecuteReader Method Example中获取的此代码块将正确地处理其使用的所有组件...

使用(SqlConnection connection = new SqlConnection(connectionString))语句块,连接到数据库并执行查询。在这种情况下,当处理SqlConnection时,它将适当地释放组件使用的所有资源。这意味着SqlCommand和SqlDataReader也会被释放。注意:这一直是我的理解。

将上述代码重构为以下代码:

using (SqlConnection connection = new SqlConnection(connectionString))
{
    connection.Open();

    using (SqlCommand command = new SqlCommand(queryString, connection))
    {
        using (SqlDataReader reader = command.ExecuteReader())
        {
            while (reader.Read())
            {
                // ...
            }
        }
    }
}

手动处理每个资源的主要优点是性能提升,因为我们不会处理Finalize方法(相对于执行Dispose()而言,这是一种更昂贵的方法)。

然而,并不意味着您想要Dispose()所有内容,在某些情况下,Finalize()方法是更好的选择。


假设到目前为止我还是状态良好的,而且我对垃圾回收器的行为理解是正确的。

我认为SqlCommand(完整代码参考此处)也会处理SqlReader的释放,这意味着你只需要这样就可以了:

using (SqlCommand command = new SqlCommand(queryString, connection))
{
    SqlDataReader reader = command.ExecuteReader();
    while (reader.Read())
    {
        // ...
    }
}

在您的情况下,这将被翻译为:

using (var cmd = data.CreateCommand(sql))
{
    var reader = cmd.ExecuteDataReader();
    // ...
}

请查看此链接:https://dev59.com/QXI-5IYBdhLWcg3wiY3a#1808056。还有它的评论:例如,不处理SqlCeCommand会导致您的移动设备非常快地耗尽内存。 - S.Serpooshan
@S.Serp System.Data.SqlServerCe.SqlCeCommandSystem.Data.SqlClient.SqlCommand 是不同的。 - Svek
是的,我知道,但正如评论中所说,这只是一个例子,用来说明不释放实现了 IDisposable 接口的对象不是一个好主意。问题取决于几个参数,而且这样做是不安全的!如果你检查 SqlCommand.Dispose 的源代码,你会发现它释放了一些东西,比如:this._cachedMetaData = null; - S.Serpooshan
如果我理解正确,在最后一个例子中,当cmd被释放时,reader不会被释放,因为它没有在自己的using语句中。 - Jure
据我所知,它确实被处理掉了,因为cmd资源被处理掉了,Finalize()方法会清理它。这不是糟糕的设计,只是没有调用Dispose()方法那么整洁。 - Svek

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接