.Net Dispose 模式 与 C++/CLI 确定性资源清理

 

1. .Net Dispose 模式

   

        CLI 所有语言支持,但是C++/CLI 在编译阶段进行了特殊处理,因此不要试图用C++/CLI 实现

 

    下面的代码,实际上C++/CLI 编译器是禁止一个类显式实现System::IDisposable 接口的。C++/CLI

 

    的处理方式将在后文讨论。

   

        C# 实现Dispose 模式MSDN 已经说的很清楚了,网上也有不少相关资料,下面的例子给出了主要的

 

    实现(其类),看注释就很清楚了。这里要说明的是,如果需要实现一个派生类,只需要重写带参数的

  

    Dispose 就可以了,注意仍然需要一个bool 型变量确保资源不被多次释放,并且永远不要抛出异常。

   

    using System;

    using System.ComponentModel;

 

    // The base class use resource

    public class MyResource: IDisposable

    {

        // Pointer to an external unmanaged resource.

        private IntPtr handle;

       

        // Other managed resource this class uses.

        private Component component = new Component();

       

        // Track whether Dispose has been called.

        private bool disposed = false;

 

        // The class constructor.

        public MyResource(IntPtr handle) {

            this.handle = handle;

        }

 

        // Implement IDisposable.

        // Do not make this method virtual.

        // A derived class should not be able to override this method.

        public void Dispose() {

            Dispose(true);

           

            // This object will be cleaned up by the Dispose method.

            // Therefore, you should call GC.SupressFinalize to

            // take this object off the finalization queue

            // and prevent finalization code for this object

            // from executing a second time.

            GC.SuppressFinalize(this);

        }

 

        // Dispose(bool disposing) executes in two distinct scenarios.

        // If disposing equals true, the method has been called directly

        // or indirectly by a user's code. Managed and unmanaged resources

        // can be disposed.

        // If disposing equals false, the method has been called by the

        // runtime from inside the finalizer and you should not reference

        // other objects. Only unmanaged resources can be disposed.

        protected virtual void Dispose(bool disposing) {

            // Check to see if Dispose has already been called.

            if(!this.disposed) {

                // If disposing equals true, dispose all managed

                // and unmanaged resources.

                if(disposing) {

                    // Dispose managed resources.

                    component.Dispose();

                }

            

                // Call the appropriate methods to clean up

                // unmanaged resources here.

                // If disposing is false,

                // only the following code is executed.

                CloseHandle(handle);

                handle = IntPtr.Zero;

            } 

            disposed = true;

        }

 

        // Use interop to call the method necessary 

        // to clean up the unmanaged resource.

        [System.Runtime.InteropServices.DllImport("Kernel32")]

        private extern static Boolean CloseHandle(IntPtr handle);

 

        // Use C# destructor syntax for finalization code.

        // This destructor will run only if the Dispose method

        // does not get called.

        // It gives your base class the opportunity to finalize.

        // Do not provide destructors in types derived from this class.

        ~MyResource() {

            // Do not re-create Dispose clean-up code here.

            // Calling Dispose(false) is optimal in terms of

            // readability and maintainability.

            Dispose(false);

        }

    }

       

    internal static class Program

    {

        private static void Main() {

            // Insert code here to create

            // and use the MyResource object.  

        }

    }

       

2. C++/CLI 确定性资源清理

 

         C++/CLI 保留了栈对象的语义(注意只是保留了栈对象的语义,不要认为托管内存回收可以


手动控制),
 同时支持析构器与终结器,对资源清理采取的手法比其他.Net语言有些特殊。下面这句话


出自
MSDN

   

    Destructors in a reference type perform deterministic clean up of your
 

resources.
Finalizers clean up unmanaged resources and can be called


deterministically by the
destructor or non-deterministically by the garbage


collector.

   

        因此在析构器里面应该清除托管资源,在终结器里面应该清除非托管资源。为了避免代码重复,


MSDN
推荐在析构器里清理托管资源外,最后调用终结器。

   

    一个C++/CLI 类中同时定义析构器与终结器(如下面的声明)

   

    ref class T

    {

       ~T()// Destructor

       !T()// Finalizer

    };

   

    语义为:

   

    void Dispose(bool disposing)

    {

        if (disposing) {

            ~T();

        } else {

            !T();

        }

    }

   

    结合Dispose模式,就不难理解为何要在析构器里调用终结器了。

   

    废话不多说,举例如下:

   

   #include <vcclr.h>

    #include <stdio.h>

    using namespace System;

    using namespace System::IO;

 

    ref class SystemFileWriter

    {

    public:

       SystemFileWriter(String^ name) : file(File::Open(name, FileMode::Append)),

           arr(gcnew array<Byte>(10)) {

            for (int i = 0; i < arr->Length; i++) {

                arr[i] = (Byte)'A';

            }

       }

 

       void WriteToFile() {      

          file->Write(arr, 0, arr->Length);

       }

 

       ~SystemFileWriter() {

          delete file;

       }

      

    private:

        FileStream^ file;

        array<Byte>^ arr;

    };

 

    ref class CRTFileWriter

    {

    public:

        CRTFileWriter(String^ name) : file(getFile(name)),

            arr(gcnew array<Byte>(10)), 
disposed(false) {

            for (int i = 0; i < arr->Length; i++) {

                arr[i] = (Byte)'B';

            }

        }

 

        void WriteToFile() {

            pin_ptr<Byte> buf = &arr[0];

            fwrite(buf, 1, arr->Length, file);

        }

 

        ~CRTFileWriter() {

            if (!disposed) {

                this->!CRTFileWriter();

                disposed = true;

            }

        }

 

        !CRTFileWriter() {

             fclose(file);         

        }

       

    private:

        FILE* file;

        array<Byte>^ arr;

        bool disposed;

 

        static FILE* getFile(String ^ n) {

            pin_ptr<const wchar_t> name = PtrToStringChars(n);

            FILE* ret = 0;

            _wfopen_s(&ret, name, L"ab");

 

            return ret;

        }

    };

 

    int main() {

        // 推荐写法,利用栈对象语义清理资源

        SystemFileWriter w1("systest.txt");

        w1.WriteToFile();

       

        // 这种写法用完后一般应该调用delete

        CRTFileWriter^ w2 = gcnew CRTFileWriter("crttest.txt");

        try {

            w2->WriteToFile();

        }

        finally {

            delete w2;

        }

 

        return 0;

    }

   

        SystemFileWriter 利用.Net BCL System::IO::FileStream 来写文件,


虽然
FileStream 内部也使用了文件句柄(非托管资源),但是FileStream本身会处理这个句柄,


所以它应该被视为托管资源。那
么就应该在析构器里面调用delete ,而不应该在终结器里调用,因


为终结器是由垃圾收集器调用的,可能在它
调用时,file的句柄已经被释放了,导致异常的发生。

 

        CRTFileWriter 使用的File 句柄是非托管资源,垃圾收集器不知道怎么关闭它,因此


要在终结器采取
关闭操作。如果客户代码调用了使用后调用了delete 或者使用栈对象语义,栈清空时,


析构器会调用,并且编译
器已经在析构器最后添加了GC.SuppriseFinalize(this); 从终结队列里


移除了,所以不必担心会增加垃圾对象
的代从而增加垃圾收集器的压力。资源回收完全可以手动控制,

就是所谓的确定性资源清理。

 

        如果客户代码使用了追踪句柄创建了引用类型,而忘记调用delete,那么析构器会在某个时


刻被调用从而做关
闭文件句柄的操作,此时会增加文件句柄被占用的时间和增加托管堆的压力。

 

        最后补充一点,在C++/CLI 里,一个类终结器的调用不会自动调用基类及类层次结构的终结器,


因此处理基类
与处理派生类具有一致性(不像C#等其他.Net语言的Dispose模式)。

posted on 2007-12-25 00:47  优哉@游哉  阅读(1455)  评论(0编辑  收藏  举报

导航