【问题标题】:Is disposing a local brush variable necessary?是否需要配置局部画笔变量?
【发布时间】:2026-02-16 08:10:01
【问题描述】:

MSDN 建议在释放最后一个引用之前释放 System.Drawing.Brush 类型的任何变量。否则,在垃圾收集器调用 Brush 对象的 Finalize 方法之前,它正在使用的资源不会被释放。

众所周知,当控制流超出其所属方法的范围时,局部变量会自动销毁。那么,如果是局部的,是否需要每次都设置一个画笔对象呢?

【问题讨论】:

  • 是的,这是必要的。这就是 GC 的工作原理。
  • As we know, local variables are destroyed automatically when the control flow goes out of the scope - 这是一种误解。 什么都没有在 C# 中发生,这是您的变量超出范围的直接后果。例如在 C++ 中,在这种情况下会调用 析构函数,但在 C# 中,只有 IDisposableusing 语句结合才能达到类似的效果。

标签: c# .net vb.net winforms visual-studio


【解决方案1】:

是的,这是必要的。你的逻辑缺陷是当你说:

众所周知,当控制流超出其所属方法的范围时,局部变量会自动销毁。

那句话是错误的。

当变量超出范围时,这意味着它们可能会在未来某个时间被垃圾收集器销毁,但很可能不会立即销毁。因此,如果立即释放系统资源很重要,您需要手动执行此操作,而不是等待垃圾收集器在将来的某个时间处理它。

这就是使用系统资源的类实现IDisposable 接口的原因。他们希望您在处理完他们后调用他们的Dispose 方法,以便他们可以立即释放系统资源。可以安全地假设您应该始终对实现IDisposable 接口的每个对象调用Dispose 方法。如果没有必要,他们不会实现该接口。

对于Brush 类,它通过GDI API 创建一个系统对象。为了通过 GDI API 绘制填充图形,您必须调用一个方法来创建一个画笔对象。 API 返回画笔对象的句柄,然后您可以在以后的 API 调用中使用该句柄来引用该画笔。当您完成画笔后,您应该调用DeleteObject API 调用以删除对象。由于 Windows 中的每个进程被限制为最多 10,000 个 GDI 对象,因此在使用完它们后删除它们非常重要,否则您将用完 GDI 对象并导致OutOfMemoryException。这就是Brush 类实现IDisposable 接口的原因——这样它就可以删除底层的GDI 对象。

建议尽可能在所有一次性对象上使用using 块。当执行离开using 块时,它会自动为您调用对象上的Dispose 方法,即使执行由于异常而离开块。

using(Brush b = New Brush())
{
    // use the brush
}

或者

Using b As New Brush()
    ' use the brush
End Using

【讨论】:

    【解决方案2】:

    众所周知,局部变量会自动销毁

    不,这是一个神话。 “破坏”这个词是完全不恰当的,这表明程序实际上努力对变量做一些特殊的事情以调用破坏。就像把一块砖扔进玻璃窗一样。这不是它的工作原理,变量只是凭空消失了。它被遗忘了,就好像它从未存在过一样。没有砖被扔。最终变量的存储被其他东西覆盖,它被重用。通常在几分之一微秒内。

    不必销毁变量是托管代码与本机代码竞争的原因。例如,C++ 编译器必须明确地执行此操作,RAII 模式是样板文件。使用引用计数的较旧的运行时实现是另一个示例,它们必须确保显式地对引用计数进行倒计时。这是托管代码不需要的额外代码,垃圾收集器知道何时使用局部变量。完成这项工作有点慢,这是 IDisposable 存在的原因。向 CLR 添加引用计数的尝试是 abysmal failure,它无法与 GC 竞争。

    使用 using 语句需要扔砖。

    【讨论】:

    • “最终变量的存储被其他东西覆盖,它被重用。通常在几分之一微秒内。”这是描述 GC 的一种非常不准确的方式。它不是简单地在一微秒内被覆盖,它会一直保持到 GC 发生并调用终结器,然后才会释放内存以供另一个对象覆盖。
    • 另一个常见的误解。 GC 堆存储对象,变量只存储对它的引用。只是一个指针。
    • 好的,但是只有对它的引用会被快速销毁。堆上的对象,实际上是重要的部分,在 GC 清理它之前仍然存在。
    • 又出现了“破坏”这个词。没有任何东西被破坏,没有砖块。将变量存储在 CPU 寄存器中并让它被另一个变量值覆盖的核心能力对于速度来说绝对是必不可少的。
    【解决方案3】:

    即使局部变量被销毁,这只是对位于托管堆上的 Brush 对象的引用。直到垃圾收集器清扫托管堆,实际对象才被销毁并释放资源。

    作为一般规则:当您完成对象后,请始终在任何实现 IDisposable 的对象上调用 Dispose

    最好的方法通常是使用using 构造,即使在异常情况下也能正确处理:

    using(var brush = CreateBrush())
    {
       brush.PaintSomethingNice();
    }
    

    【讨论】:

      【解决方案4】:

      是的,这是必要的。一般来说,如果它实现了IDisposable,你应该在完成后将其丢弃。当它超出范围时,这只意味着它有资格进行垃圾收集。 GC可能很长一段时间都不会处理,所以你应该立即处理。

      【讨论】: