将对象设置为null vs Dispose()

时间:2009-02-22 00:26:58

标签: c# .net disposable

我对CLR和GC的工作方式很着迷(我正在通过C#,Jon Skeet的书籍/帖子等阅读CLR来扩展我的知识。)

无论如何,说:

之间有什么区别
MyClass myclass = new MyClass();
myclass = null;

或者,通过使MyClass实现IDisposable和析构函数并调用Dispose()?

另外,如果我有一个带有using语句的代码块(例如下面的代码),如果我单步执行代码并退出using块,那么对象是在处理垃圾收集时发生的吗?如果我在use block中调用Dispose()会发生什么?

using (MyDisposableObj mydispobj = new MyDisposableObj())
{

}

流类(例如BinaryWriter)有一个Finalize方法吗?我为什么要用它?

3 个答案:

答案 0 :(得分:198)

将处理与垃圾收集分开是很重要的。它们是完全不同的东西,有一个共同点,我将在一分钟内到达。

Dispose,垃圾收集和完成

当您编写using语句时,它只是try / finally块的语法糖,因此即使Dispose语句正文中的代码抛出一个using,也会调用IDisposable例外。它意味着该对象在块的末尾被垃圾收集。

Disposal是关于非托管资源(非内存资源)。这些可能是UI句柄,网络连接,文件句柄等。这些是有限的资源,因此您通常希望尽快释放它们。只要您的类型“拥有”非托管资源,您应该直接(通常通过IntPtr)或间接(例如通过StreamSqlConnection等)实施~Foo()

垃圾收集本身只是关于内存 - 只有一点点扭曲。垃圾收集器能够找到无法再引用的对象,并释放它们。它不会一直寻找垃圾 - 只有当它检测到它需要时(例如,如果堆的一个“代”耗尽内存)。

扭曲是终结。垃圾收集器会保留一个不再可访问的对象列表,但它们有一个终结器(在C#中写为FileStream,有些令人困惑 - 它们与C ++析构函数完全不同)。它在这些对象上运行终结器,以防它们需要在释放内存之前进行额外的清理。

在该类型的用户忘记以有序方式处理它的情况下,终结器几乎总是用于清理资源。因此,如果您打开Dispose但忘记致电Closenull,则终结器将最终为您发布基础文件句柄。在一个写得很好的程序中,终结者几乎不会在我看来发射。

将变量设置为null

将变量设置为StringBuilder sb = new StringBuilder(); sb.Append("Foo"); string x = sb.ToString(); // The string and StringBuilder are already eligible // for garbage collection here! int y = 10; DoSomething(y); // These aren't helping at all! x = null; sb = null; // Assume that x and sb aren't used here 的一个小问题 - 为了垃圾收集,这几乎不需要。如果它是一个成员变量,你有时可能想要这样做,尽管根据我的经验,不再需要一个对象的“部分”。当它是一个局部变量时,JIT通常足够聪明(在发布模式下),以便知道何时不再使用引用。例如:

null

可能值得将局部变量设置为SomeObject foo = new SomeObject(); for (int i=0; i < 100000; i++) { if (i == 5) { foo.DoSomething(); // We're not going to need it again, but the JIT // wouldn't spot that foo = null; } else { // Some other code } } 的一次是当你处于循环中时,循环的某些分支需要使用变量但是你知道你已经达到了你没有达到的程度。例如:

FileStream

实施IDisposable / finalizers

那么,你自己的类型应该实现终结器吗?几乎肯定不是。如果你只是间接地持有非托管资源(例如你有FileStream作为成员变量)那么添加你自己的终结器将无济于事:流几乎肯定有资格获得垃圾当你的对象被收集时,你可以依靠IntPtr有一个终结器(如果需要的话 - 它可能会引用别的东西等)。如果您想“近乎”直接持有非托管资源,SafeHandle是您的朋友 - 需要一些时间才能继续使用,但这意味着您将almost never need to write a finalizer again。如果您对资源有一个非常直接的处理(SafeHandle),通常只需要一个终结器,您应该尽快转移到Dispose。 (那里有两个链接 - 理想情况下读两个。)

乔·达菲有一个very long set of guidelines around finalizers and IDisposable(与许多聪明的人合写)值得一读。值得注意的是,如果你密封你的课程,它会让生活变得更轻松:覆盖Dispose(bool)以调用新的虚拟{{1}}方法等的模式仅在你的类被设计用于继承时才有意义。

这有点絮絮叨叨,但请你澄清一下你想要的地方:))

答案 1 :(得分:21)

处理对象时,将释放资源。为变量赋值null时,您只需更改引用。

myclass = null;

执行此操作后,myclass所指的对象仍然存在,并将一直持续到GC清理它为止。如果显式调用Dispose,或者它在using块中,则将尽快释放所有资源。

答案 2 :(得分:5)

这两项行动彼此没有太大关系。 当您将引用设置为null时,它只是这样做。它本身并不会影响所引用的类。 你的变量不再指向它曾经使用过的对象,但是对象本身没有变化。

当你调用Dispose()时,它是对象本身的方法调用。无论Dispose方法做什么,现在都在对象上完成。但这不会影响您对该对象的引用。

唯一的重叠区域是没有对对象的更多引用时,最终会收集垃圾。如果类实现了IDisposable接口,那么在对象被垃圾收集之前将调用Dispose()。

但是,在将引用设置为null之后,这不会立即发生,原因有两个。 首先,可能存在其他引用,因此它根本不会收集垃圾,其次,即使这是最后一个引用,所以现在它已经准备好被垃圾收集,在垃圾收集器决定删除之前不会发生任何事情对象。

在对象上调用Dispose()不会以任何方式“杀死”对象。它通常用于清理,以便之后可以安全删除对象 ,但最终,Dispose没有任何神奇之处,它只是一个类方法。