在Dispose()中将对象设置为null(Nothing)有意义吗?

33

Dispose()方法中将自定义对象设置为null(在VB.NET中是Nothing),这样做有意义吗?它能够防止内存泄漏吗还是毫无用处?

我们来看两个例子:

public class Foo : IDisposable
{
    private Bar bar; // standard custom .NET object

    public Foo(Bar bar) {
        this.bar = bar;
    }
    public void Dispose() {
        bar = null; // any sense?
    }
}

public class Foo : RichTextBox
{
    // this could be also: GDI+, TCP socket, SQl Connection, other "heavy" object
    private Bitmap backImage; 

    public Foo(Bitmap backImage) {
        this.backImage = backImage;
    }

    protected override void Dispose(bool disposing) {
        if (disposing) {
            backImage = null;  // any sense?
        }
    }
}
8个回答

24

从个人角度考虑,我倾向于这样做,理由如下:

  • 如果有人忘记释放Foo(例如在事件中),任何下游对象(在本例中是Bitmap)仍然可以在将来的某个时间(当GC感觉合适时)被收集;它很可能只是一个包装未受管理资源的浅层包装器,但即使是微不足道的帮助也很重要。
  • 更重要的是,我现在可以使用这个字段(在方法等中)检查是否已处理,并在其为null时抛出ObjectDisposedException

1
你在调用Dispose()方法后,有多久保留对象的引用? - Brian Rasmussen
8
请注意,“accidentally” 和 “event” 这两个词,同时请注意 不一定是编写使用我的组件的代码的人。我无法修复他们的代码,但我可以使我的组件表现良好。 - Marc Gravell
2
抱歉,如果我表达不当。我并不反对这种做法。我只是更喜欢没有这些冗余的稍微简单一点的代码。 - Brian Rasmussen
1
我假设如果你需要实现 IDisposable 接口(某些特定资源需要被释放),那么你应该这样做,这样你可以更彻底一些。大概你不会在每个地方都实现 IDisposable 接口,所以你可以这么做。 - MarkJ
1
+1. 在Dispose方法中(如果存在),应始终将Events设置为null,以防订阅方忘记取消挂钩。在每个项目中都会看到这种情况发生,如果不取消挂钩,则会导致内存泄漏。 - Lee Grissom

21
Dispose() 的目的是允许清理由垃圾回收器未处理的资源。通常对象会被垃圾回收器自动处理,因此在正常情况下没有必要将引用设置为 null。
唯一的例外是当你希望调用者在调用 Dispose() 之后还应该保留实例时。这种情况下,将内部引用设置为 null 可能是一个好主意。然而,可处理的实例通常会同时进行处理和释放。在这些情况下,这并不会产生很大的区别。

4
你始终需要决定清理依赖项的位置。如果你知道位图在别处没有使用,Foo就应该调用 Dispose()。否则,它应该保持原样,让调用者处理清理工作。将局部引用设置为 null 没有额外的好处。当 Foo 的实例被回收时,位图的实例也会被回收,除非调用者仍然持有对它的引用。 - Brian Rasmussen
2
@serhio - 如果你想在 Foo 使用 Bitmap 对象后立即释放它所占用的资源(没有其他人在使用它),那么 Foo.Dispose 应该调用 backImage.Dispose。 - Gishu
@serhio - 这是正确的,只有在一个对象完全由你的类“拥有”时才调用 Dispose - Daniel Earwicker
1
将字段设置为null会使该字段不再被引用。即使在Dispose中没有对其进行任何操作,该引用也会一直与其容器相关联,直到垃圾回收器决定不再关联。将其设置为null可以使垃圾回收器尽早地解除对象的关联,从而减轻其负担。请参见Marc的答案。 - Peter Ritchie
@PeterRitchie:你是在谈论这个具体的情况还是在争论一般情况下应该尽快将所有引用设置为null? - Brian Rasmussen
显示剩余12条评论

4

这几乎没什么用。在旧的COM/VB时代,将其设置为NULL会递减引用计数。

但是在.NET中并非如此。当您将bar设置为null时,您并没有销毁或释放任何内容。您只是更改了bar指向的引用,从您的对象更改为“null”。您的对象仍然存在(尽管现在,由于没有任何引用它,它最终将被垃圾回收)。除了少数例外情况和大多数情况下,这与您一开始没有将Foo IDisposable相同。

IDisposable的主要目的是允许您释放非托管资源,例如TCP套接字或SQL连接。通常通过调用非托管资源提供的任何清理函数来完成,而不是通过将引用设置为“null”。


1
好的,如果我有一个TCP套接字而不是* Bar *怎么办?将其设置为null是否无效?因为它是通过参数传递的,"某人"可能会使用这个对象... - serhio
1
是的,这样做是无用的。如果你有一个TCP套接字,你可以通过调用套接字的.Close()方法来释放它。同样的事情也适用于SQL连接。将其设置为“null”实际上并没有做任何事情,只是改变了你正在使用的对象的引用。 - Dave Markle
2
@AMissico:将其设置为“nothing”而不是让它超出范围?只有在长时间内处于范围内但未使用时才会有影响。 - John Saunders
1
超出作用域发生在对象被清理时,而不是调用Dispose时。因此,像Aaronaught指出的那样,将其设置为nothing有助于短暂存在的对象。 - AMissico
2
@AMissico:超出范围的意思是指引用超出了其作用域。例如,在方法结束时针对本地变量发生的情况。 - John Saunders
显示剩余3条评论

1

如果你想防止已释放的拥有实例被重新使用,这样做是有意义的。

当你将可释放字段的引用设置为null时,你可以确保不再使用这些实例。

你将不会因为使用已释放的拥有实例而得到ObjectDisposedException或其他无效状态(如果你没有检查null值,可能会得到NullReferenceException)。

只要所有的IDisposable对象都有一个IsDisposed属性和/或在被释放后抛出ObjectDisposedException,这对你来说可能没有意义 - 但有些对象可能违反了这个原则,将它们设置为null可以防止产生不必要的影响。


1
在C#中,将对象设置为null只是释放对该对象的引用。
因此,在C#中管理对象的理论最佳做法是在Dispose方法中释放引用,这样GC就有可能在处理已释放的对象之前收集被引用的对象。由于两者很可能在同一次运行中被收集,GC很可能会认识到被引用的对象仅被一个已释放的类型所引用,因此两者都可以被收集。
此外,释放引用的需求非常小,因为您可释放的类的所有公共成员应在类已释放时抛出异常。因此,在释放引用方法后,不会成功访问您的被引用对象。

谢谢 Dave,已更改有关 VB.NET 的信息。 - Oliver Friedrich
那么,在设置为Nothing时,C#和VB.NET有什么区别?我在C#中提出了这个问题以便更易读,但我的真实项目是用VB.NET编写的。 - serhio
2
对于您的用途,没有区别。但VB.NET是一种可怕的语言。在VB.NET中,如果你设置Dim x as integer = nothing,然后打印"x"的值,你会得到0。在C#中,它只是不编译,因为“int”是一个值类型,“null”严格是一个引用。所以它们的行为并不完全相同。但对于像IDisposable对象这样的引用类型,它们的行为完全相同。 - Dave Markle

1
在VB.NET中,将已声明的私有带事件对象设置为Nothing是有意义的。
这样做将从这些对象中删除使用Handles关键字的处理程序。

0
一般情况下不需要设置为null。但是假设你的类中有一个重置功能。
那么你可能会这样做,因为你不想调用两次dispose,因为其中一些dispose可能没有正确实现并抛出System.ObjectDisposed异常。
private void Reset()
{
    if(_dataset != null)
    {
       _dataset.Dispose();
       _dataset = null;
    }
    //..More such member variables like oracle connection etc. _oraConnection
 }

0

dispose() 的目的是清理不受管理的资源。TCP 连接、数据库连接、其他数据库对象以及许多这样的不受管理的资源都应该由开发人员在 dispose 方法中释放。因此,它确实有意义。


我需要为使用GDI+ Bitmap和简单自定义.NET对象Bar的示例进行翻译吗?我不会对它们进行处理,因为它们是通过参数传递而来,而不是由对象创建的。 - serhio

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