111

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

无论如何,说:

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

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

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

using (MyDisposableObj mydispobj = new MyDisposableObj())
{

}

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

4

3 回答 3

223

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

Dispose, 垃圾收集和终结

当您编写using语句时,它只是 try/finally 块的语法糖,因此Dispose即使using语句主体中的代码抛出异常也会调用它。这并不意味着该对象在块的末尾被垃圾收集。

处置是关于非托管资源(非内存资源)。这些可能是 UI 句柄、网络连接、文件句柄等。这些资源有限,因此您通常希望尽快释放它们。您应该IDisposable在您的类型“拥有”非托管资源时直接(通常通过 a IntPtr)或间接(例如通过 a Stream、 aSqlConnection等)实现。

垃圾收集本身只与内存有关 - 有一点点扭曲。垃圾收集器能够找到不再被引用的对象,并释放它们。但它并不总是寻找垃圾——只有当它检测到它需要时(例如,如果堆的“一代”内存不足)。

转折是定稿。垃圾收集器保留一个不再可访问的对象列表,但这些对象有一个终结器(用~Foo()C# 编写,有点令人困惑——它们与 C++ 析构函数完全不同)。它在这些对象上运行终结器,以防它们需要在释放内存之前进行额外的清理。

终结器几乎总是用于在该类型的用户忘记有序处置资源的情况下清理资源。所以如果你打开 aFileStream但忘记调用Disposeor Close,终结器最终会为你释放底层文件句柄。在我看来,在一个编写良好的程序中,终结器几乎不应该触发。

将变量设置为null

关于将变量设置为的一小点null- 为了垃圾收集,这几乎不需要。如果它是成员变量,您有时可能想要这样做,尽管根据我的经验,不再需要对象的“部分”很少见。当它是一个局部变量时,JIT 通常足够聪明(在发布模式下)知道什么时候你不会再次使用引用。例如:

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

可能值得设置局部变量的一次是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 
    }
}

实现 IDisposable/终结器

那么,你自己的类型应该实现终结器吗?几乎可以肯定不是。如果您只是间接持有非托管资源(例如,您将 aFileStream作为成员变量),那么添加您自己的终结器将无济于事:当您的对象存在时,流几乎肯定有资格进行垃圾收集,因此您可以依赖FileStream有一个终结器(如果有必要 - 它可能指的是别的东西,等等)。如果你想“几乎”直接持有一个非托管资源,SafeHandle是你的朋友 - 这需要一些时间来开始,但这意味着你几乎 不需要再次编写终结器。如果您对资源 (an IntPtr) 有真正的直接句柄,您通常只需要一个终结器,并且您应该寻求移动到SafeHandle你尽快做。(那里有两个链接 - 最好都阅读。)

Joe Duffy 有很长的一套关于终结器和 IDisposable(与许多聪明人共同编写)的指南,值得一读。值得注意的是,如果你密封你的类,它会让生活变得更容易:重写Dispose调用新的虚拟Dispose(bool)方法等的模式只有在你的类是为继承而设计时才相关。

这有点杂乱无章,但请要求澄清您想要的地方:)

于 2009-02-22T09:47:51.360 回答
22

释放对象时,资源将被释放。当您将 null 分配给变量时,您只是在更改引用。

myclass = null;

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

于 2009-02-22T00:31:53.833 回答
8

这两个操作彼此没有太大关系。当您设置对 null 的引用时,它只是这样做。它本身并不影响被引用的类。您的变量不再指向它曾经指向的对象,但对象本身并没有改变。

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

唯一重叠的地方是,不再引用一个对象时,它最终会被垃圾回收。如果该类实现了 IDisposable 接口,则在对象被垃圾收集之前将调用 Dispose()。

但是,在您将引用设置为 null 后,这不会立即发生,原因有两个。首先,其他引用可能存在,所以它根本不会被垃圾收集,其次,即使那是最后一个引用,所以它现在已经准备好被垃圾收集了,在垃圾收集器决定删除之前什么都不会发生物体。

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

于 2009-02-22T00:58:02.677 回答