1. .Net Dispose 模式

   

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


 

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

 

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

   

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


 

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

  

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

   

using
using

 
// The base class use resource
public class
    {
// Pointer to an external unmanaged resource.
private
        
// Other managed resource this class uses.
private Component component = new
        
// Track whether Dispose has been called.
private bool disposed = false;

 
// The class constructor.
public
this.handle = handle;
        }

 
// Implement IDisposable.
// Do not make this method virtual.
// A derived class should not be able to override this method.
public void
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.
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
// 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;
            }  
true;
        }

 
// Use interop to call the method necessary  
// to clean up the unmanaged resource.
"Kernel32")]
private extern static

 
// 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.
false);
        }
    }
        
internal static class
    {
private static void
// 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();// Destructor
       !T();// Finalizer
    };

   

    语义为:

void Dispose(bool
    {
if
            ~T();
else
            !T();
        }
    }

   

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

   

    废话不多说,举例如下:

   

#include <vcclr.h>
#include <stdio.h>
using namespace
using namespace

 
ref class
    {
public:
       SystemFileWriter(String^ name) : file(File::Open(name, FileMode::Append)), 
gcnew array<Byte>(10)) {
for (int
'A';
            }
       }

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

 
       ~SystemFileWriter() {
delete
       }
       
private:
        FileStream^ file;
array<Byte>^ arr;
    };

 
ref class
    {
public:
        CRTFileWriter(String^ name) : file(getFile(name)), 

             arr(gcnew array<Byte>(10)), disposed(false) {for (int
'B';
            }
        }

 
void
pin_ptr<Byte> buf = &arr[0];
            fwrite(buf, 1, arr->Length, file);
        }

 
        ~CRTFileWriter() {
if
this->!CRTFileWriter();
true;
            }
        }

 
        !CRTFileWriter() {
             fclose(file);          
        }
        
private:
        FILE* file;
array<Byte>^ arr;
bool

 
static
pin_ptr<const wchar_t> name = PtrToStringChars(n);
            FILE* ret = 0;
"ab");

 
return
        }
    };

 
int
// 推荐写法,利用栈对象语义清理资源
"systest.txt"); 
        w1.WriteToFile();
        
// 这种写法用完后一般应该调用delete
gcnew CRTFileWriter("crttest.txt"); 
try
            w2->WriteToFile();
        }
finally
delete
        }

 
return
    }

   

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


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


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


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


 

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


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


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


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

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


 

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


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


 

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


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