重要的是将处理和垃圾收集分开。它们是完全不同的东西,只有一个共同点,我马上就会谈到。
当您编写using语句时,它只是try/finally块的语法糖,以便在using语句主体中的代码引发异常时仍调用Dispose。这并不意味着对象在块结束时被垃圾收集。
处理是关于非托管资源(非内存资源)。这些可以是UI句柄、网络连接、文件句柄等有限资源,因此通常希望尽早释放它们。每当类型“拥有”非托管资源时(通常通过IntPtr直接或间接地(例如通过Stream、SqlConnection等)),应实现IDisposable。
垃圾收集本身仅涉及内存,有一个小变化。垃圾回收器能够找到不能再引用的对象并释放它们。但它并不总是查找垃圾 - 只有在检测到需要(例如,如果堆的一个“代”用尽了内存)时才进行查找。
变化是finalization。垃圾回收器保留了一个不再可达但具有终结器(在C#中写为~Foo(),有点令人困惑 - 它们与C++析构函数完全不同)的对象列表。它运行这些对象的终结器,以防它们在释放内存之前需要进行额外的清理。
几乎总是使用终结器来清除资源,以防用户在有序方式下忘记处理它。因此,如果您打开FileStream但忘记调用Dispose或Close,则最终器将为您“最终”释放底层文件句柄。在编写良好的程序中,我的意见是几乎永远不应触发终结器。
关于将变量设置为null的一个小点 - 几乎从不需要为了垃圾收集而这样做。如果它是成员变量,您可能有时想这样做,尽管根据我的经验,“对象”的“部分”很少再次需要。当它是局部变量时,JIT通常足够聪明(在发布模式下)以知道何时不再使用引用。例如:
StringBuilder sb = new StringBuilder();
sb.Append("Foo");
string x = sb.ToString();
int y = 10;
DoSomething(y);
x = null;
sb = null;
只有在循环中,当您知道某些分支不再需要使用变量时,可能值得将本地变量设置为null
。例如:
SomeObject foo = new SomeObject();
for (int i=0; i < 100000; i++)
{
if (i == 5)
{
foo.DoSomething();
foo = null;
}
else
{
}
}
实现IDisposable/finalizers
那么,你自己的类型是否应该实现finalizers呢?几乎肯定不需要。如果你只是间接持有非托管资源(例如,你有一个FileStream作为成员变量),那么添加自己的终结器是没有帮助的:当您的对象可回收时,流几乎肯定也可以进行垃圾回收,因此您可以依赖于FileStream具有终结器(如果必要-它可能引用其他内容等)。如果您想“几乎”直接拥有非托管资源,则
SafeHandle
是您的朋友-它需要一些时间才能启动,但这意味着您将
几乎永远不需要再编写终结器。通常只有在您对资源拥有真正直接的处理句柄(IntPtr)时才需要终结器,并且您应该尽快转移到
SafeHandle
。 (有两个链接-最好都阅读。)
Joe Duffy有一篇
非常长的关于finalizers和IDisposable的指南(与许多聪明的人共同撰写),值得阅读。请注意,如果您封闭了您的类,那么生活会变得更加轻松:只有在您的类设计用于继承时,覆盖
Dispose
以调用新的虚拟
Dispose(bool)
方法等模式才相关。
这有点冗长,请在需要澄清时提出问题 :)