safehandle 和析构函数
safehandle 是一种析构机制,她和析构函数有什么分别。
首先要理解析构函数。析构函数在.net中是没有顺序的,因此你不能假定另一个对象的析构函数在你之后运行,哪怕它是你的成员!如果你的成员也有析构函数,那么你能做什么,什么不应该做?
第一,在析构函数运行时,你不应该假设它没清理资源,而去试图清理它。合理的做法是它应该自己实现dispose模式,你在dispose(true)段落可以调用它的dispose()函数.因为dispose是可多次重入的,因此不会有问题。如果它没有,那就别去理他,因为它的析构函数会释放它的非托管资源。这样的好处是,我可以分别控制每一个实现dispose模式的对象成员的控制时机,而dispose(false)也就是析构函数,能够作为防御性代码,帮你把忘记的“非托管”资源清理掉。
第二,一个实现了dispose的.net对象,外部应该怎样看它?不应该把它所控制的资源看作非托管资源,既然他实现了dispose,也就是“有自行管理”的,因此只需要在dispose(true)段落调用它的dispose函数,让其在显式清理实际时产生效果便可。这是我对dispose模式的理解。dispose模式分两类资源,一类是托管,一类是非托管。托管和非托管并不是纯粹站在.net角度出发,而是应该理解为“自己管理”和“其他对象管理”的差别。你的对象自己打开文件,就自己负责关闭,而其他对象,包括成员对象,他们控制的资源就由他们控制,在当前立场上看,都是“受托管的”。
第三,你的对象的 dispose 应该被显式调用(也就是最外层对象)!这个是dispose模式的关键,因为析构函数只是负责自身非托管资源的释放,它没有参与整套资源管理流程。如果你不显式调用(或者另外编写代码),清理流程是不可能正确执行的。
第四。为何析构函数不负责dispose的所有内容,首先是析构顺序的不确定性,而资源管控流程需要顺序;其次,析构函数没有明确的调用时机,而dispose可以在任意时刻调用。
然后是safehandle,safehandle据说有经过优化,但是它也不会抢先在析构函数阶段运行,在我测试中是这种情况。因此,我不知道优化在那里了。只是添加了一种模式,比析构函数更舵控制,毕竟是外部独立的类,而且系统已经针对多种资源提供了恰当的子类,可惜没有针对com对象资源。
总的来说,最佳实践是:
1.如果safehandle子类有的系统资源,如句柄,非托管内存等等,用safehandle 模式。
2.如果子成员正确实现了dispose,把它视为托管资源,在dispose(true)中调用。
3.如果子成员没有实现dispose,但是控制了相关资源,也就是你要负责子对象相关资源的显式控制,千万别用析构函数清理,因为析构函数阶段,子成员可能已经把它关联的资源给释放掉了(未必以正确的方式,只是对资源失去控制力),你无法再清理。因为GC对任意对象的析构函数调用顺序是不确定的。(这一状况只能要求你在编程阶段正确调用dispose,而绝不能遗忘调用,否则就有资源泄漏。com for .net就是如此设计的)
4.一般情况,dispose(true)负责对象链的清理流程,dispose(false)即析构函数,负责自身非托管资源。如果没有这部分可以不写析构函数。
5.dispose的实现方式要规范。最终dispose要显式调用!
规范的dispose该注意哪些,这里补充一下:
private bool disposedValue = false; // 要检测冗余调用 protected virtual void Dispose(bool disposing) { if (!disposedValue) { if (disposing) { // TODO: 释放托管状态(托管对象)。 //这里应该包含成员对象的dispose //大部分被包装的资源的处理过程都写在这里,比如退出过程:先保存,然后关闭,等等 //safehandle.Dispose(); } // TODO: 释放未托管的资源(未托管的对象)并在以下内容中替代终结器。 // TODO: 将大型字段设置为 null。 //这里是自身管理的一些资源的释放,比如通过c api调用的一些非托管资源 app = null; disposedValue = true; } } //TODO: 仅当以上 Dispose(bool disposing) 拥有用于释放未托管资源的代码时才替代终结器。 ~Excel() { // 请勿更改此代码。将清理代码放入以上 Dispose(bool disposing) 中。 Dispose(false); } // 添加此代码以正确实现可处置模式。 void IDisposable.Dispose() { // 请勿更改此代码。将清理代码放入以上 Dispose(bool disposing) 中。 Dispose(true); // TODO: 如果在以上内容中替代了终结器,则取消注释以下行。 //也就是有析构函数就有下面这行代码,表示显式运行dispose时,GC不需要再运行析构函数,提高性能 GC.SuppressFinalize(this); }