什么时候需要使用Dispose?

13

当你有如下代码:

Bitmap bmp = new Bitmap ( 100, 100 );
Graphics g = Graphics.FromImage ( bmp );

Pen p = new Pen ( Color.FromArgb ( 128, Color.Blue ), 1 );
Brush b = new SolidBrush ( Color.FromArgb ( 128, Color.Blue ) );

g.FillEllipse ( b, 0, 0, 99, 99 );    
g.FillRegion ( b, pictureBox1.Region );

pictureBox1.BackColor = Color.Transparent;
pictureBox1.Image = bmp;

您需要处理笔和刷吗?位图和g呢?

我的主要问题是,如果这些需要手动处理,为什么它们不会在超出作用域后立即被处理?如果您没有手动处理它们,那么会发生什么?延迟导致人们手动处理吗?

6个回答

17

是的,你必须将它们处理掉 - 不仅仅是笔和刷子,还有 BitmapGraphics

它们不会在超出范围时被处理掉,因为变量本身是引用而不是对象,C# 编译器无法知道所有权是否仍然属于这些引用(例如,在理论上,FillEllipse 可能会记住给定的引用,并在以后的某个时刻尝试使用它 - 请记住,语言编译器对库语义没有任何特殊知识!)。

如果您想表明所有权仅限于该作用域,则可以使用 using 语句:

using (Bitmap bmp = new Bitmap ( 100, 100 ))
using (Graphics g = Graphics.FromImage ( bmp ))
using (Pen p = new Pen ( Color.FromArgb ( 128, Color.Blue ), 1 ))
using (Brush b = new SolidBrush ( Color.FromArgb ( 128, Color.Blue ) ))
{
    g.FillEllipse ( b, 0, 0, 99, 99 );    
    g.FillRegion ( b, pictureBox1.Region );
}

这将使编译器自动插入对Dispose的调用,确保一旦离开相应的using范围(无论是正常离开还是通过控制转移,如returnbreak,或异常),所有对象都将被处理。

如果您来自C ++背景,则C#中的usingconst std :: auto_ptr直接类似,只是它在这里是一种语言结构,并且只能用于局部变量(即不能用于类字段)。


1
真的吗?有趣。我以前只在Graphics对象中见过这种用法。是否有微软网站可以更详细地讨论这个问题? - derGral
4
确实需要调用Dispose或使用using语句,但不正确的是如果某些API正在使用它,Pen否则将不会在作用域外被释放。 Pen有一个终结器,在它变得不可达时(对于短寿命对象,可能很快),会在某个未指定的时间运行,当这种情况发生时,它会执行相当于Dispose的操作。 - Daniel Earwicker
1
请参阅:http://msdn.microsoft.com/zh-cn/library/system.drawing.pen.dispose.aspx——“调用Dispose...否则,它正在使用的资源直到垃圾回收器调用Pen对象的Finalize方法才会被释放。” - Daniel Earwicker
3
如果一个对象实现了IDisposable接口,那么就应该有某个地方的代码来对其进行处理。在这种情况下,很明显除了我们自己的代码之外,没有其他代码会执行这个任务。Earwicker所说的也是正确的,如果您忘记释放资源,还有一个终结器机制可以进行清理,但由于不确定它何时启动(它甚至可能在应用程序关闭时才启动),如果您不显式调用Dispose方法,可能会导致严重泄漏;通常情况下,对于这种情况依赖终结器机制是一个非常糟糕的想法。 - Pavel Minaev
3
当你将图片分配给“Image”属性时,控件会使用它,因此在使用中不应该处理掉它。 - Pavel Minaev
显示剩余3条评论

6

我知道其他人在这里放置了代码示例,但既然我开始了,我就来完成它:

using (Bitmap bmp = new Bitmap(100, 100))
{
  using (Graphics g = Graphics.FromImage(bmp))
  {
    using (Pen p = new Pen(Color.FromArgb(128, Color.Blue), 1))
    {
      using (Brush b = new SolidBrush(Color.FromArgb(128, Color.Blue)))
      {
        g.FillEllipse(b, 0, 0, 99, 99);
        g.FillRegion(b, pictureBox1.Region);

        pictureBox1.BackColor = Color.Transparent;
        pictureBox1.Image = bmp;
      }
    }
  }
}

我在代码中经常使用using,因为它会自动调用Dispose()方法释放对象,即使在using块中抛出异常。我在SharePoint项目中也经常使用它(但这是另一个故事...)。

3
正确,但现在习惯于将多个使用情况折叠成一个括号对,例如 Pavels 的示例。 - H H
2
很酷,我以前没见过那种技巧。我想我从现在开始会使用它(无恶意) :) - Jason Evans
昨天我回答了一个与将位图分配给 pictureBox1.Image 并处理它的问题有关的问题,我的答案与您的完全相同(我已经删除了我的答案)。他们告诉我图片也会被处理。在这种情况下,我还能使用 using 吗? - Abdusalam Ben Haj
请参考我已删除的回答,以获取更多解释。链接 - Abdusalam Ben Haj

3

C#并不会在变量超出其作用域时立即“销毁”或释放它们。

这些类很可能会在它们的特殊Finalizer方法中自动释放它们所持有的非托管资源,当这些类在超出作用域后的某个不确定时间被垃圾回收时,该方法将被调用。

但是依靠这一点是依赖于你无法控制的事情,而且可能要等一段时间才能发生。

如果该类实现了IDisposable接口,则最佳做法是在某个地方手动调用Dispose()方法,或者最好将其包装在using块中。这样你就可以确信:

A. 非托管资源一定被释放了。

B. 非托管资源尽可能快地被释放了。


3
如果正确使用一次性模式,Dispose 并不是绝对必要的 -- 当对象被终结时,它将被调用,因此您不会泄漏资源或任何其他东西。
但是,礼貌起见,在完成使用对象后立即调用Dispose是好习惯,因为可处置对象通常直接控制通常有限的本机资源。对象通常不会立即被终结/收集,因此这些资源仅在您不再使用该对象时浪费。处理程序立即释放这些资源,以便其他程序的其他部分可以使用它们(或在某些情况下,由其他程序使用)。
请注意,using块在完成使用对象后自动处理该对象,这就是为什么很少在using块中看到Dispose的原因。
简而言之:如果对象实现了IDisposable,并且您的代码创建了它(即:如果它不是像Pens.Blue或您在OnPaint等中传递的Graphics这样的系统对象),则在完全完成使用它时应将其处置--无论是通过调用Dispose,还是通过调用指定调用Dispose的其他方法(Close是常见的方法),或者使用using块。您不必处置它,但您几乎总是应该这样做。

1
很多对象如果被遗弃,就不会自行清理。虽然在某些情况下清理相对简单,但在其他情况下却基本上是不可能的。除非知道可以安全地遗弃特定类别的对象,否则应该假定在未调用Dispose的情况下遗弃对象将导致糟糕的后果。 - supercat
任何实现IDisposable接口的对象,如果被遗弃,都会正确地清理自己。BCL中的所有可处理对象都是如此。任何没有正确实现该接口的对象都应该找到其创建者并解雇他们,因为这并不难做到——在几乎所有情况下,您只需在终结器中调用Dispose方法即可。任何与本机资源打交道的人都应该深刻了解当它们未被正确释放时会发生什么。而那些没有直接涉及本机资源的人则不需要实现IDisposable接口。 - cHao
你知道有没有任何一种实现ChangeCounter对象的方法,可以按照所描述的方式运行,并在不依赖于传入的INotifyPropertyChanged对象以外的方式下进行清理?鉴于最流行的实现RemoveHandler方法的方式并不真正线程安全(如果另一个线程持有从中尝试删除事件的对象的锁定,则会阻塞),调用PropertyChanged.RemoveHandler似乎非常危险。 - supercat
我可以看到许多类似于INotifyPropertyChanged的接口定义方式,可以在订阅者被遗弃时进行安全清理,但我没有看到一个类从INotifyProperty事件订阅后如何清理其遗弃实例,也没有看到任何实现INotifyProperty可以自动清理遗弃订阅者的方法。您能否提出任何方法,无论多么笨拙,都不依赖于接口中未指定的行为? - supercat
至于如何实现这样的类,嗯...你可以考虑在实际订阅者周围包装一个包装器。订阅者将具有对包装器的弱引用。您处理包装器的引用,并在完成后让其超出范围并成为GC可回收的。当订阅者接收到事件通知并看到包装器已被GC'ed时,它可以从通知列表中删除自己,这可能会删除仅剩的唯一引用并使订阅者本身成为GC可回收的。 - cHao
显示剩余7条评论

1

是的,bmp、g、b和p都是IDisposable,你应该Dispose()它们所有。最好使用using() {}块来处理。

当你使用Pen p2 = Pens.Blue;时,有一些例外情况,你不应该处理p2。这被称为库存项。同样适用于Brushes.Black等。

至于为什么,对于所有可处理类,原因都是相同的。.Net不使用引用计数,因此当引用超出范围时没有(也不能有)立即操作。

将其留给垃圾收集器最终会释放它们,但这是(非常)低效的。我知道一个ASP.NET(!)应用程序由于未及时处理图形句柄而因此失败。它正在生成图像。


谢谢,Henk。如果你在Pens.Blue上调用Dispose会发生什么?这是不好的吗? - Joan Venge
1
你知道吗,我其实不太确定。在尝试之前,我猜测可能会立即出现异常或者什么都不会发生。 - H H
1
我尝试过:你会收到一个 System.ArgumentException 异常,其中包含不允许更改的消息。 - H H

1

Dispose用于处理非托管资源。

因此,作为一个经验法则,我会在using语句中包装任何IDisposable对象的实例化,这样我就不必担心Pen有哪些非托管资源。


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