第四节:对托管资源使用终结器

重要提示:有的人可能有这样的心态,永远不要对托管资源使用终结器,我在很大程度上赞成这个观点,所以可以完全跳过本节,对托管资源使用终结器,是非常高的编码方式,只有极少数情况下才应该使用,要是使用必须对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方法。

  1. 可终结的对象需要花更长的时间来分配,因为指向他们的指针必须放到终结者列表中。
  2. 可终结的对象会提升到比较老的一代,这会增大内存压力 ,并在垃圾回收器判定对象为垃圾时,阻止回收对象的内存。除此之外,该对象直接或间接引用的所有对象也会被提升。
  3. 可终结的对象导致应用程序速度变慢,这是因为每个对象在回收时,必须对他进行额外的处理。

除此之外,注意无法控制Finalize方法在什么时候运行,Finalize方法在垃圾回收器发生时运行,而垃圾回收期可能在应用程序请求更多的内存时发生。另外CLR不保证每个Finalize方法的调用顺序。因此,在写一个Finalize方法时,应避免访问定义了Finalize方法的其它类型的对象,那些对象可能已经终结,然而,可以完全放心的访问值类型的实例,或者访问没有定义Finalize方法的引用类型的实例。调用静态方法也得小心。

因为这些方法可能在内部访问以终结的对象,导致静态方法的行为变得无法预测。