把垃圾处理和垃圾收集分开是很重要的.它们是完全不同的东西,有一点是相同的,我马上就要谈到.
100, garbage collection and finalization
编写using
语句时,它只是try/finally块的语法糖,因此即使using
语句体中的代码抛出异常,也会调用Dispose
.这意味着对象在块的末尾被垃圾收集.
处置量约为unmanaged resources(非内存资源).这些可能是UI句柄、网络连接、文件句柄等.这些资源有限,所以您通常希望尽快释放它们.当您的类型"拥有"非托管资源时,您应该实现IDisposable
,直接(通常通过IntPtr
)或间接(例如通过Stream
、SqlConnection
等).
垃圾收集本身只与内存有关-只有一点小问题.垃圾回收器能够找到不能再引用的对象,并将其释放.不过,它并不总是查找垃圾-只有当它检测到需要查找垃圾时(例如,如果某个"一代"堆耗尽了内存).
扭转率是finalization.垃圾收集器保存一个对象列表,这些对象不再可达,但是有终结器(在C#中写为~Foo()
,有点令人困惑-它们与C++析构函数完全不同).它在这些对象上运行终结器,以防它们在释放内存之前需要执行额外的清理.
如果类型的用户忘记了有序地处理资源,那么终结器几乎总是用来清理资源.因此,如果你打开一个FileStream
,但忘记调用Dispose
或Close
,终结器将eventually为你释放底层文件句柄.在我看来,在一个写得很好的程序中,终结器几乎不应该开火.
Setting a variable to 100
关于将变量设置为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
}
}
Implementing IDisposable/finalizers个
那么,您自己的类型是否应该实现终结器呢?几乎可以肯定不会.如果您只有indirectly个持有非托管资源(例如,您有一个FileStream
作为成员变量),那么添加您自己的终结器也无济于事:当您的对象符合垃圾回收条件时,几乎可以肯定流有资格进行垃圾回收,所以您可以只依赖FileStream
具有终结器(如果需要-它可以引用其他东西,等等).如果您想"几乎"直接持有非托管资源,SafeHandle
是您的朋友--这需要一点时间才能上手,但这意味着您将拥有almost和never need to write a finalizer again.您通常只需要一个终结器,如果您对资源(ANIntPtr
)有一个真正直接的句柄,并且您应该尽可能快地迁移到SafeHandle
.(这里有两个链接-最好是两个都读.)
乔·达菲(Joe Duffy)有一本very long set of guidelines around finalizers and IDisposable页(与许多聪明人合著)值得一读.值得注意的是,如果你密封了你的类,它会让生活变得更加简单:重写Dispose
来调用一个新的虚拟Dispose(bool)
方法等的模式只有在你的类是为继承而设计的时候才相关.
这有点漫无边际,但请在你想要的地方要求澄清:)