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模式)。