重要提示:有的人可能有这样的心态,永远不要对托管资源使用终结器,我在很大程度上赞成这个观点,所以可以完全跳过本节,对托管资源使用终结器,是非常高的编码方式,只有极少数情况下才应该使用,要是使用必须对Finalize方法中的调用的代码有一个全面和深刻的认识。另外,还必须保证调用的代码的行为在未来的版本中不会发生改变。具体的说,Finalize方法中调用的任何代码都不能使用其他任何可能已终结的对象。
虽然终结操作是专门来释放本地资源,但偶尔也用于托管资源,下面这个类造成计算机在垃圾回收器每执行一次回收就发出响铃声。
sealed class GCBeep
{
~GCBeep()
{
if (!AppDomain.CurrentDomain.IsFinalizingForUnload() && !Environment.HasShutdownStarted)
new GCBeep();
}
}
为了使用这个类,只需要构建该类的一个实例。然后,每次执行垃圾回收,都会调用对象的Finalize方法,该方法会调用Beep并构造一个新的GCBeep对象。下次垃圾回收时,这个新的GCBeep对象的Finalize方法将得到调用。如此反复,下面程序演示该操作:
static void Main(string[] args)
{
new GCBeep();
for (Int32 x = 0; x < 100000000; x++)
{
Console.WriteLine(x);
Byte[] b = new Byte[100];
}
}
另外注意的是,即使类的实例构造跑出了异常,类型的Finalize方法也会被调用。因此,你的Finalize方法不应假定对象处于良好、一致的状态。下面的代码演示了这一点。
sealed class TempFile
{
private String m_filename = null;
private FileStream m_fs;
public TempFile(string filename)
{
//下面这行代码可能抛出异常
m_fs = new FileStream(filename, FileMode.Create);
m_filename = filename;
}
~TempFile()
{
//正确的做法是测试filename是否为空,不能保证filename已经在构造中初始化。
if (m_filename != null) File.Delete(m_filename);
}
}
另外一种做法,
sealed class TempFile
{
private String m_filename = null;
private FileStream m_fs;
public TempFile(string filename)
{
try
{
//下面这行代码可能抛出异常
m_fs = new FileStream(filename, FileMode.Create);
m_filename = filename;
}
catch
{
GC.SuppressFinalize(this);//告诉GC不要调用Finalize方法
throw;//让调用者知道错误出现
}
}
~TempFile()
{
//这里不用判断,因为只有在构造成功之后才执行这里
File.Delete(m_filename);
}
}
设计一个类型时,出于以下几个性能方面的原因,最好是避免使用Finalize方法。
- 可终结的对象需要花更长的时间来分配,因为指向他们的指针必须放到终结者列表中。
- 可终结的对象会提升到比较老的一代,这会增大内存压力 ,并在垃圾回收器判定对象为垃圾时,阻止回收对象的内存。除此之外,该对象直接或间接引用的所有对象也会被提升。
- 可终结的对象导致应用程序速度变慢,这是因为每个对象在回收时,必须对他进行额外的处理。
除此之外,注意无法控制Finalize方法在什么时候运行,Finalize方法在垃圾回收器发生时运行,而垃圾回收期可能在应用程序请求更多的内存时发生。另外CLR不保证每个Finalize方法的调用顺序。因此,在写一个Finalize方法时,应避免访问定义了Finalize方法的其它类型的对象,那些对象可能已经终结,然而,可以完全放心的访问值类型的实例,或者访问没有定义Finalize方法的引用类型的实例。调用静态方法也得小心。
因为这些方法可能在内部访问以终结的对象,导致静态方法的行为变得无法预测。