C#中的"using"语句在什么情况下最有用?

15

所以,当退出using块时,using语句会自动调用被“使用”的对象的dispose方法,对吗?

但是这种做法什么时候必要或有益呢?

例如,假设你有这个方法:

public void DoSomething()
{
    using (Font font1 = new Font("Arial", 10.0f))
    {
        // Draw some text here
    }
}
这里需要使用using语句吗,因为对象是在方法中创建的?当方法退出时,Font对象不会被自动释放吗?还是Dispose方法会在方法退出后的其他时间运行?比如,如果方法像这样:
public void DoSomething()
{
    Font font1 = new Font("Arial", 10.0f);

    // Draw some text here
}

// Is everything disposed or cleared after the method has finished running?

仅仅因为某个对象超出作用域并不意味着所有资源都会被清理。如果您有未托管对象的句柄(比如 COM 对象),当您的托管对象死亡时,这些未托管对象将变成孤儿。它们将在进程的生命周期内挂起,从而导致内存泄漏。 - Rex M
在你的例子中,你明确地告诉对象进行清理操作,这对于数据库连接或者如果你持有文件锁是非常有用的。如果你没有这样做,对象的 finalizer 将会在以后某个时刻运行(并不特别是在方法结束之后),进行清理操作——但可能太晚了。 - meandmycode
14个回答

20

“using”语句在处理非托管对象(如数据库连接)时非常有用。

这样,无论代码块中发生什么情况,连接都会被关闭和释放。

欲了解更多讨论,请参阅CodeProject上的这篇文章: http://www.codeproject.com/KB/cs/tinguusingstatement.aspx


从内存角度来看,对象仍在等待清理...但好处是对象拥有的任何资源都可以在其Dispose方法中释放。但是退出方法会调用对象的Dispose方法吗? - John B
是的,在using代码块中的return语句将会调用Dispose方法释放该对象。终结器和垃圾回收器会在未确定的时间后触发。 - Jeff Fritz
仅仅存在一个函数本身并不能以确定的方式处理对象的释放。而使用using语句则可以。 - Joel Coehoorn
Dispose方法将在没有使用using语句的情况下被调用,但你不知道何时会被调用。该对象可能会一直保持打开状态很长时间。使用using语句可以确保它立即关闭,即使抛出异常也是如此。 - Joel Coehoorn
好的,我现在明白了。非常棒的交流,谢谢所有参与者! - John B
显示剩余9条评论

13

这个:

public void DoSomething()
{
    using (Font font1 = new Font("Arial", 10.0f))
    {
        // Draw some text here
    }
}

直接映射到这个:

public void DoSomething()
{
    {
        Font font1;
        try
        {
            font1 = new Font("Arial", 10.0f);
            // Draw some text here
        }
        finally
        {
            IDisposable disp = font1 as IDisposable;
            if (disp != null) disp.Dispose();
        }
    }
}

注意finally块:即使发生异常,对象也会被处理。 还要注意额外的匿名作用域块:这意味着不仅对象被处理了,而且它也超出了作用域。

这里的另一个重要事项是处理是立即保证发生的。它是确定性的。 如果没有using语句或类似的结构,对象仍将在方法结束时超出范围,然后最终可能会被收集。资源理想情况下将被销毁以便可以被系统回收。但是,“最终”可能需要一段时间,而“理想情况下”和“将”是非常不同的事情。

因此,“最终”并不总是足够好的。 像数据库连接、套接字、信号量/互斥体和(在本例中)GDI资源等资源通常受到严格限制,需要立即清理。 using语句将确保这种情况发生。


+1 是为了指出“using”如何控制变量的作用域! - James Schek

13

如果不使用(或手动调用Dispose()),对象最终将被处理,但时间不是确定的。也就是说,它可能会立即发生,在两天后发生,或者在某些情况下根本不会发生。

对于像网络连接这样的事物,当您使用完毕时,希望连接关闭,而不是“随时”,否则它会闲置并霸占一个套接字。

此外,对于诸如互斥锁之类的东西,您不希望它们在“随时”释放,否则死锁很可能会发生。


虽然这可能是真的,但这并不是规则。垃圾回收器不会自己调用Dispose方法,因此开发人员需要创建一个终结器来确保资源得到清理,即使没有调用Dispose方法。 - Adam Robinson
同意。虽然写得好的终结器会做到dispose应该做的事情。 - C. K. Young

9
使用using语句可以强制执行确定性的资源释放,即释放资源。在上面的示例中,如果您不使用"using"语句,该对象将被释放,但仅当为相关类(在您的示例中为Font类)实现了推荐的可处置模式(即从类终结器处处置资源)时才会被释放。需要注意的是,using语句只能用于实现IDisposable接口的对象。正是这个接口的存在允许使用 "Dispose" 方法。
此外,在垃圾收集器决定收集您的超出范围的Font对象时,基础资源才会被释放。.NET编程(以及大多数具有垃圾收集器的语言)中的一个关键概念是,仅因为对象超出了作用域并不意味着它已经被最终化/释放/销毁等。相反,垃圾收集器将在它确定的时间执行清理 - 而不是在对象超出作用域时立即执行清理。
最后,using语句“内置”了一个try/finally结构,以确保无论包含的代码是否引发异常,Dispose都将被调用。

1
它根本不强制执行终结。它强制执行确定性处理。我认为将终结和处理分开放在脑海中是一个好主意。(许多可处置的类型根本没有终结器。) - Jon Skeet
完全同意 - 我选择的术语很不恰当。我想我会编辑问题以反映这一点。Finalizers是一个完全不同的结构。 - Peter Meyer

2
这是“using”关键字的实际作用(基于您提供的示例)。
Font font1 = new Font(...);
try
{
    // code that uses font...
}
finally
{
    if (font1 != null)
        font1.Dispose();
}

所以你不需要担心异常会导致变量未被处理。

实际上,在调用Dispose()之前,它还会检查引用不为null。 - Lucero
使用has一个额外的好处是它控制了作用域。这样,您就不能在调用dispose()之后尝试使用font1。 - James Schek

2

对我来说,这似乎非常简单。

如果一个类实现了IDisposable接口,那么它几乎是在“请求”您请调用Dispose方法,以便在使用完实例后立即释放资源。我建议在创建此类实例时应该使用以下模式:

using (var instanceName = new DisposableClass())
{
    // Your code here
}

这个东西很简单,很干净,适用于除了WCF代理类以外的所有东西都能正常使用。但是对于WCF代理类来说就有问题了,详见http://www.iserviceoriented.com/blog/post/Indisposable+-+WCF+Gotcha+1.aspx


不是每次都这样做。一个可接受的替代方案是将一个或多个IDisposable对象包装在一个实现了IDisposable接口的类中。然后,在这个类的Dispose方法和终结器中,对它的依赖项进行处理。 - Joel Coehoorn
+1 虽然针对 IDisposable == 要求安全释放,这甚至可以说是“恳求”或“要求”。 - Joel Coehoorn
@Joel:如果有多个被包装,那么暴露出来的是一个单一的IDisposable,应该按照这种模式实现。该模式适用于您创建的实例,这些实例实现了IDisposable。 - John Saunders

2

"Using"在资源需要被释放且实现了IDisposable接口时发挥作用。


2
当方法退出时,字体对象会被销毁吗?
不会,它将变得无法引用,因此有资格进行垃圾回收。除非其他东西(例如另一个数据结构中保存的引用)保留了引用。
Dispose方法在方法退出后的另一个时间运行吗?
是的,在分配的内存不多的进程中,这可能是方法退出后相当长的时间。
垃圾回收本质上是异步和懒惰的,因此如果内存不太紧张,它是确保内存被释放的好方法;但对于几乎任何其他资源来说都很差。

0

我喜欢这样使用它们:

    static public void AddSampleData(String name)
    {
        String CreateScript = GetScript(String.Format("SampleData_{0}", name));
        using (IDbConnection MyConnection = GetConnection())
        {
            MyConnection.Open();
            IDbCommand MyCommand = MyConnection.CreateCommand();
            foreach (String SqlScriptLine in CreateScript.Split(';'))
            {
                String CleanedString = SqlScriptLine.Replace(";", "").Trim();
                if (CleanedString.Length == 0)
                    continue;

                MyCommand.CommandText = CleanedString;
                int Result = MyCommand.ExecuteNonQuery();
            }
        }
    }

它们非常适用于那些不想要加入错误检查代码的情况,只需清理并传递错误即可。另一种替代方法如下:

    static public void AddSampleData(String name)
    {
        String CreateScript = GetScript(String.Format("SampleData_{0}", name));

        IDbConnection MyConnection = null;
        try
        {
            IDbConnection MyConnection = GetConnection();
            MyConnection.Open();
            IDbCommand MyCommand = MyConnection.CreateCommand();
            foreach (String SqlScriptLine in CreateScript.Split(';'))
            {
                String CleanedString = SqlScriptLine.Replace(";", "").Trim();
                if (CleanedString.Length == 0)
                    continue;

                MyCommand.CommandText = CleanedString;
                int Result = MyCommand.ExecuteNonQuery();
            }
        }
        finally
        {
            if (MyConnection != null
                && MyConnection.State == ConnectionState.Open)
                MyConnection.Close();
        }
    }

说实话,哪种方法更容易阅读?表单也是同样的道理:

    public void ChangeConfig()
    {
        using (ConfigForm MyForm = new ConfigForm())
        {
            DialogResult ConfigResult = MyForm.ShowDialog();
            if (ConfigResult == DialogResult.OK)
                SaveConfig();
        }

        ConfigForm MyForm = new ConfigForm();
        DialogResult ConfigResult = MyForm.ShowDialog();
        MyForm.Dispose();
        if (ConfigResult == DialogResult.OK)
            SaveConfig();
    }

0

无论何时您想要在对象确定性地调用清理代码并且不受异常影响时,它们都非常有用(因为using语句实际上只是一个try/finally)。

它们通常用于清理非托管资源,但您可以根据需要使用它们。


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