【问题标题】:Using using to dispose of nested objects使用 using 处理嵌套对象
【发布时间】:2011-02-10 06:47:13
【问题描述】:

如果我有这样的嵌套对象的代码,我是否需要使用嵌套的 using 语句来确保 SQLCommand 和 SQLConnection 对象都被正确处理,如下所示,或者如果实例化 SQLCommand 的代码我可以在外部 using 语句中。

using (var conn = new SqlConnection(sqlConnString))
{
    using (var cmd = new SqlCommand())
    {
        cmd.CommandType = CommandType.Text;
        cmd.CommandText = cmdTextHere;
        conn.Open();

        cmd.Connection = conn;
        rowsAffected = cmd.ExecuteNonQuery();
    }
}

【问题讨论】:

    标签: c# .net garbage-collection idisposable


    【解决方案1】:

    是的。你可以像这样清理它

    using (SqlConnection conn = new SqlConnection(sqlConnString))
    using (System.Data.SqlClient.SqlCommand cmd = new System.Data.SqlClient.SqlCommand())
    {
       // code here
    }
    

    但您仍然希望每个 IDisposable 对象都有一个 using

    编辑:考虑这个使用内部using语句的例子。

    class A : IDisposable
    {
        public void Dispose()
        {
            Console.WriteLine("A Disposed");
        }
    }
    
    class B : IDisposable
    {
        public void Dispose()
        {
            Console.WriteLine("B Disposed");
        }
    }
    

    代码

    using (A a = new A())            
    {
        B b = new B();
    }
    

    在块的末尾,正确地处理了 A。 B会发生什么?好吧,它超出了范围,只是等待垃圾收集。 B.Dispose() 没有被调用。另一方面

    using (A a = new A())
    using (B b = new B())
    {
    }
    

    当执行离开块(实际上是)时,编译后的代码执行对每个对象的 Dispose() 方法的调用。

    【讨论】:

    • 有什么特别的原因让它总是明确的吗?这个答案偏离了问题的真正目的。
    • @Nayan,请参阅我对您的回答的评论。
    • 警告:不要对 Stream 类执行此操作,因为它们拥有传递的流!见stackoverflow.com/questions/1065168/…
    【解决方案2】:

    您应该对两者都调用 dispose,但是,如果您这样做会更容易阅读:

    using (SqlConnection conn = new SqlConnection(sqlConnString)) 
    using (SqlCommand cmd = new SqlCommand()) 
    { 
         cmd.CommandType = CommandType.Text; 
         cmd.CommandText = cmdTextHere; 
         conn.Open(); 
    
         cmd.Connection = conn; 
         rowsAffected = cmd.ExecuteNonQuery(); 
    } 
    

    由于创建了一个隐式块(如 if 语句或 for 语句),并且由于 using 是一个完整的块,因此您不需要大括号,而且在我看来它看起来更整洁。有人会说你应该总是使用大括号,因为很容易意外添加第二条语句并造成错误,但在这种情况下,我认为这不太可能。

    【讨论】:

    • 整洁,是的。但它仍然没有回答这个问题。 ://
    • 是的,确实如此。你应该在两者上调用 dispose,因此对两者都使用 using 语句。
    【解决方案3】:

    您可以省略 SqlCommand 周围的使用。 GC 最终会为您清理它。但是,我强烈建议您不要这样做。我会解释原因。

    SqlCommand 间接继承自 System.ComponentModel.Component,因此它继承了它的 Finalizer 方法。不在SqlCommand 上调用 dispose 将确保命令在超出范围后至少提升一代(.NET 垃圾收集器是generational gc)。例如:当命令在 gen 1 中时,它将继续到 gen 2。可终结对象在内存中保留的时间更长,以确保终结器可以安全运行。但是不仅命令本身保存在内存中,而且它引用的所有对象都与它一起保存到那一代。它将引用的对象是SqlConnectionSqlParameter 对象的列表、可能很大的CommandText 字符串,以及它引用的许多其他内部对象。那个内存只有在那一代被收集的时候才能被移除,但是越是越高的代越少被清扫。

    因此,不调用会给终结器线程带来额外的内存压力和额外的工作。

    当 .NET 无法分配新内存时,CLR 将强制对所有代进行垃圾收集。在此之后,运行时通常会再次有足够的空间来分配新对象。但是,当内存中有大量对象仍需要提升到下一代(因为它们是可终结的,或者被可终结的对象引用)时,当这种强制收集发生时,CLR 可能无法释放足够的内存。 OutOfMemoryException 将是这个结果。

    我必须承认我从未见过这种情况,因为开发人员并没有只处理他们的 SqlCommand 对象。但是,我在生产系统中看到了很多 OOM,这是由于没有正确处理对象造成的。

    我希望这能提供一些关于 GC 工作原理的背景知识,以及不正确处理(可终结)对象的风险。我总是处理所有一次性物品。虽然查看 Reflector 可以证明这不一定适用于某种类型,但这种编程会导致代码难以维护,并使代码依赖于类型的内部行为(并且这种行为将来可能会改变)。

    【讨论】:

    • 感谢您的解释,史蒂文!
    【解决方案4】:

    要回答您的问题,是的,您可以这样做。

    由于SqlCommand对象被大括号限制,当执行出块时会被GC回收。

    其他答案也可以,但他们并没有完全回答你的问题:)

    【讨论】:

    • 垃圾回收与调用 .Dispose() 不同。此外,GC 是不确定的,您无法准确知道何时收集对象。
    • 依赖 GC 收集 SqlCommand 实例会导致非常讨厌的内存“泄漏”,在内存中留下很多实例。
    • 我同意马雷克的观点。请阅读我的回答。这是马雷克答案的长版本:-)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-04-04
    • 2018-07-29
    • 1970-01-01
    • 2017-06-07
    • 1970-01-01
    • 2014-09-17
    相关资源
    最近更新 更多