.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模式)。
 
                    
                 
                
            
         
 浙公网安备 33010602011771号
浙公网安备 33010602011771号