将对象设置为null与Dispose()方法的区别

15 浏览
0 Comments

将对象设置为null与Dispose()方法的区别

我对CLR和GC的工作方式很着迷(我正在通过阅读《CLR via C#》,Jon Skeet的书籍/文章等来扩展我的知识)。无论如何,下面两种说法有什么区别:\n

MyClass myclass = new MyClass();
myclass = null;

\n或者,让MyClass实现IDisposable和一个析构函数,并调用Dispose()方法呢?\n此外,如果我有一个带有using语句的代码块(如下所示),如果我逐步执行代码并退出using块,那么对象是在那时被释放的还是在进行垃圾回收时?如果我在using块中调用Dispose()会发生什么?\n

using (MyDisposableObj mydispobj = new MyDisposableObj())
{
}

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

0
0 Comments

在编程中,有时需要处理对象的释放和清理问题。在这方面,常见的两种做法是将对象设置为null和调用Dispose()方法。然而,这两种方法并没有太多的关联。本文将探讨这个问题的出现原因以及解决方法。

将对象设置为null只是简单地将引用指向null。它本身并不会对被引用的类产生任何影响。你的变量不再指向之前的对象,但对象本身并没有改变。

而当调用Dispose()方法时,它是对对象本身的方法调用。Dispose()方法所做的任何操作都是在对象上执行的。但这并不影响你对对象的引用。

这两种方法唯一的交集是,当没有更多的引用指向一个对象时,它最终会被垃圾回收。如果该类实现了IDisposable接口,那么在对象被垃圾回收之前,将会调用Dispose()方法。

但在你将引用设置为null之后,这并不会立即发生,原因有两点。首先,可能还存在其他的引用,所以对象不会被垃圾回收,其次,即使那是最后一个引用,对象现在准备好被垃圾回收了,但在垃圾收集器决定删除对象之前,什么都不会发生。

调用Dispose()方法并不会“销毁”对象。它通常用于清理工作,以便对象在被删除之前可以安全地进行清理,但最终,Dispose并没有什么神奇之处,它只是一个类方法。

如果你有很多MB的数据,并且想要调试这个语句,以证明这个条件是真的还是假的,是否有办法查看内存使用情况呢?

// 以下是一种查看内存使用情况的方法

using System;

using System.Diagnostics;

class Program

{

static void Main()

{

// 获取当前进程的内存使用情况

Process currentProcess = Process.GetCurrentProcess();

long memoryUsed = currentProcess.WorkingSet64;

Console.WriteLine("Memory Used: {0} bytes", memoryUsed);

}

}

通过以上代码,我们可以获取当前进程的内存使用情况。你可以在你的语句执行之前和之后使用这段代码,然后对比两次输出的内存使用情况,以确定你的条件是否符合预期。

0
0 Comments

当你调用Dispose方法释放一个对象时,资源会被释放。当你将一个变量设置为null时,你只是改变了一个引用。

myclass = null;

执行完这行代码后,myclass所引用的对象仍然存在,并且直到垃圾回收器清理它之前都将继续存在。如果显式调用了Dispose方法,或者对象在using块中,任何资源都会尽快被释放。

在执行这行代码之后,对象可能已经在这行代码之前被垃圾回收器回收了。JIT是很聪明的,使得这种代码几乎总是无关紧要的。

将对象设置为null可能意味着对象持有的资源永远不会被释放。垃圾回收器不会进行释放操作,只会进行终结操作,所以如果对象直接持有非托管资源并且其终结器不进行释放操作(或者没有终结器),那么这些资源将会泄漏。这是需要注意的一点。

0
0 Comments

在这篇文章中,我们讨论了在释放资源和垃圾收集之间的区别。释放资源是指释放非内存资源,例如UI句柄、网络连接和文件句柄等。垃圾收集则是指释放内存。这两者是完全独立的概念,但在某些情况下有共同点。

首先,我们提到了`Dispose`、垃圾收集和finalization之间的关系。通过使用`using`语句,我们可以确保即使在`using`语句块中的代码抛出异常,`Dispose`方法也会被调用。然而,这并不表示对象在块结束时就会被垃圾收集。

我们需要实现`IDisposable`接口来释放非内存资源,这些资源可以是通过直接拥有的非托管资源(通常通过`IntPtr`)或者间接拥有的资源(例如`Stream`、`SqlConnection`等)。

垃圾收集只涉及内存的释放。垃圾收集器会检查哪些对象已经无法引用,并释放它们所占用的内存。垃圾收集器并不总是持续查找垃圾对象,只有在需要时(例如当某个堆的“代”用完内存时)才会进行垃圾收集。

然而,垃圾收集器还有一个特殊的地方,即finalization。垃圾收集器会维护一个不再可达但具有finalizer(在C#中表示为`~Foo()`)的对象列表。在释放这些对象的内存之前,垃圾收集器会运行它们的finalizer,以便进行额外的清理工作。finalizer通常用于在类型的使用者忘记有序地释放资源时进行资源清理。因此,如果打开了一个`FileStream`但忘记调用`Dispose`或`Close`,finalizer最终会释放底层文件句柄。在良好编写的程序中,finalizer几乎不会触发。

关于将变量设置为null,这通常不是为了垃圾收集而需要的。在大多数情况下,当变量是局部变量时,JIT编译器会在编译时就知道你不会再使用该引用。唯一可能需要将局部变量设置为null的情况是在循环中,当循环的某些分支需要使用该变量,但你知道已经到达不再需要使用它的点。

在实现`IDisposable`和finalizers方面,我们几乎不需要自己实现finalizers。如果你只是间接拥有非托管资源(例如作为成员变量的`FileStream`),则添加自己的finalizer并不会有什么帮助。`FileStream`几乎肯定会在你的对象被垃圾收集时也会被垃圾收集,所以你可以依赖于`FileStream`自身的finalizer。如果你想直接拥有非托管资源(例如`IntPtr`),你应该尽快使用`SafeHandle`来替代,因为它会更加方便,几乎可以避免使用finalizer。

总结一下,为了释放非内存资源,我们应该实现`IDisposable`接口,并在`Dispose`方法中释放这些资源。垃圾收集器会自动释放内存,但是我们可以通过finalizer来进行额外的清理工作。在大多数情况下,我们不需要自己实现finalizer,而是依赖于已有的finalizer。设置变量为null通常不是为了垃圾收集而需要的,但在某些特殊情况下可以考虑使用。最后,我们应该尽量避免使用finalizer,并且在类设计时尽量将类设为sealed,以简化代码实现。

0