将处理与垃圾收集分开很重要。它们是完全不同的东西,有一个共同点,我稍后会谈到。
Dispose
, 垃圾收集和终结
当您编写using
语句时,它只是 try/finally 块的语法糖,因此Dispose
即使using
语句主体中的代码抛出异常也会调用它。这并不意味着该对象在块的末尾被垃圾收集。
处置是关于非托管资源(非内存资源)。这些可能是 UI 句柄、网络连接、文件句柄等。这些资源有限,因此您通常希望尽快释放它们。您应该IDisposable
在您的类型“拥有”非托管资源时直接(通常通过 a IntPtr
)或间接(例如通过 a Stream
、 aSqlConnection
等)实现。
垃圾收集本身只与内存有关 - 有一点点扭曲。垃圾收集器能够找到不再被引用的对象,并释放它们。但它并不总是寻找垃圾——只有当它检测到它需要时(例如,如果堆的“一代”内存不足)。
转折是定稿。垃圾收集器保留一个不再可访问的对象列表,但这些对象有一个终结器(用~Foo()
C# 编写,有点令人困惑——它们与 C++ 析构函数完全不同)。它在这些对象上运行终结器,以防它们需要在释放内存之前进行额外的清理。
终结器几乎总是用于在该类型的用户忘记有序处置资源的情况下清理资源。所以如果你打开 aFileStream
但忘记调用Dispose
or 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)
方法等的模式只有在你的类是为继承而设计时才相关。
这有点杂乱无章,但请要求澄清您想要的地方:)