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?
4

14 回答 14

20

'using' 语句在处理非托管对象时最有用,例如数据库连接。

这样,无论代码块中发生什么,连接都会被关闭和处理。

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

于 2009-04-15T16:37:15.607 回答
13

如果没有using(或手动调用Dispose()),对象最终将被处理掉,只是不是在确定的时间。也就是说,它可以在两天内立即发生,或者(在某些情况下)永远不会发生。

对于诸如网络连接之类的事情,您希望连接在完成后关闭,而不是“无论何时”,否则它将在占用套接字时闲置。

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

于 2009-04-15T16:39:59.953 回答
12

这:

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 语句将确保发生这种情况。

于 2009-04-15T17:02:21.257 回答
9

using 构造强制执行确定性处置——即资源的释放。在上面的示例中,是的,如果您不使用“使用”语句,则对象将被释放,但前提是已实现推荐的一次性模式(即,从类终结器中释放资源,如果适用对于有问题的类(在您的示例中为 Font 类)。需要注意的是,using 结构只能用于实现IDisposable接口的对象。正是该接口在对象上的存在允许使用“调用”该Dispose方法。

此外,只有当垃圾收集器决定收集超出范围的 Font 对象时,才会释放底层资源。.Net 编程(以及大多数带有垃圾收集器的语言)中的一个关键概念是,仅仅因为一个对象超出范围并不意味着它已完成/释放/销毁等。相反,垃圾收集器将执行清理 -在它确定的时间向上 - 不是在对象超出范围时立即向上。

最后,using 语句“烘焙”了一个 try/finally 结构,以确保无论包含的代码是否引发异常都会调用 Dispose。

于 2009-04-15T16:41:57.000 回答
2

当需要释放资源并实现 IDisposable 接口时,“Using”就会发挥作用。

于 2009-04-15T16:37:35.013 回答
2

这是 using 真正的作用(基于您的示例)

Font font1 = new Font(...);
try
{
    // code that uses font...
}
finally
{
    if (font1 != null)
        font1.Dispose();
}

因此,您无需担心导致变量未释放的异常。

于 2009-04-15T16:39:52.247 回答
2

当方法退出时,Font 对象不会被丢弃吗?

不,它将变得未被引用,因此有资格进行垃圾收集。除非其他东西(例如,保存在另一个数据结构中的引用)保持引用。

或者 Dispose 方法是否在方法退出后的另一个时间运行?

是的,在一个没有分配太多内存的过程中,在方法退出后可能需要相当长的时间。

垃圾收集本质上是异步和惰性的,因此如果内存不太有限,它是确保释放内存的好方法;但几乎所有其他资源都很差。

于 2009-04-15T16:48:58.387 回答
2

这对我来说似乎很简单。

如果一个类实现了 IDisposable,那么它几乎是要求您在完成后立即对您创建的任何实例调用 Dispose。这是我建议应该使用的模式,无论何时创建这样的实例:

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

它很简单,很干净,它适用于除了 WCF 代理类之外的所有东西,这些代理类被破坏了。对于那些,请参阅http://www.iserviceorientated.com/blog/post/Indisposable+-+WCF+Gotcha+1.aspx

于 2009-04-15T17:08:17.607 回答
0

当您希望在对象上确定地调用清理代码并且不考虑异常(因为using语句实际上只是 a try/finally)时,它们在任何情况下都很有用。

大多数情况下,它们用于清理非托管资源,但您可以随意使用它们。

于 2009-04-15T16:38:48.347 回答
0

它是 try-catch-finally 块和对 Dispose 方法的调用之上的纯语法糖。它甚至不必定义词法范围,因为您可以将其交给实例变量。基本上,它使您的代码更简洁,更易于维护。

于 2009-04-15T16:41:55.627 回答
0

Dispose 在退出 using 语句时被调用,因为它是由该构造显式调用的。当变量超出范围(退出方法)时,不会显式调用 dispose 方法。

您可能观察到的行为看起来是这样的,通常实现 IDisposable 的东西也会在类析构函数中调用 Dispose 方法,并且可以在变量超出范围后立即调用析构函数,但不能保证。析构函数由垃圾收集器调用。

于 2009-04-15T16:42:21.380 回答
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();
    }
于 2009-04-15T16:43:43.813 回答
0

关于确定性与非确定性以及幕后using工作如何正确的所有答案。

但是要选择您的特定示例,请不要忘记几乎所有System.Drawing(GDI+ 包装器)对象也包含对非托管内存的引用,因此在广泛使用这些对象而不正确处理它们时可能会遇到麻烦(哪里using是最简单的方法)。

于 2009-04-15T16:50:49.987 回答
0

我在这里问了一个非常相似的问题:

必须在 C# 中手动清理的资源?

我得到了一个很好的建议,形式如下:

一般来说,如果某个东西有一个 Dispose 方法,你应该在用完它后调用它,或者,如果可以的话,将它包装在 using 语句中。

于 2009-04-15T18:38:12.663 回答