口气。终结器除了清理非受控资源之外不应该执行其它任何操作。如果某个终结器由于什么原因使某个对象又可以到达了,那么该对象就恢复(resurrected)了。即使它是从"昏睡"状态醒来的,它也是"活着"的。下面是一个很明显的例子:
public class BadClass
{
// 保存某个全局对象的引用
private readonly ArrayList _finalizedList;
private string _msg;
public BadClass( ArrayList badList, string msg )
{
// 缓冲该引用
_finalizedList = badList;
_msg = (string)msg.Clone();
}
~BadClass()
{
// 把该对象添加到列表中。这个对象是可到达的,不再是垃圾了。它回来了!
_finalizedList.Add( this );
}
}
当某个BadClass对象执行自己的终结器的时候,它向全局列表上添加了对自己的引用。这仅仅使自己可到达了,它活了过来!但是这样操作所带来的问题使任何人都会感到胆怯。该对象已经被终结了,因此垃圾收集器相信不用再次调用它的终结器了。你真的需要终结一个被恢复的对象的时候,终结操作却不会发生了。其次,你的一些资源可能不能用了。GC不会把终结器队列中的对象可以到达的任何对象从内存中移除,但是它可能已经终结了这些对象。如果是这样的话,那些对象一定不能再次使用了。尽管BadClass的成员仍然存在于内存中,它们却像被处理过或被终结了一样。在C#语言中没有控制终结次序的途径。你不能使这种构造工作更可靠。不要尝试!
除了学院的练习作业之外,我从来没有见到过如此明显地使用被恢复对象的代码。但是我看到有些代码有这个倾向,它们在终结器中试图执行某些实际工作,当终结器调用的某些函数保存了对该对象的引用的时候,它就正在把对象变成活动的状态。原则上我们必须非常仔细地检查finalizer和Dispose方法中任何代码。如果有些代码除了释放资源之外还执行了其它的操作,我们就需要再检查一次。这些操作在未来可能引起
程序bug。请移除这些操作,并确保finalizer和Dispose()方法只释放资源,不作其它任务事务。
在受控环境中,你不必为自己建立的每个类型编写终结器,你只需要为存储非受控类型,或者包含了实现IDisposable接口的成员的类型编写终结器。即使你只需要Disposable接口,不需要finalizer,也应该同时实现整个模式。否则,你会使衍生类的标准Dispose思想的实现变得很复杂,从而限制了衍生类的功能。请遵循前面谈到的标准的Dispose思想,这将使你、你的类的用户、从你的类型建立衍生类的用户的生活更加轻松。