首先,您似乎误解了托管和非托管资源的概念。wordDoc
不是非托管资源,它是托管资源,它恰好直接持有非托管资源或充当其他IDisposable
对象的包装器(您不在乎两者中的哪一个是真的)。清楚这一点很重要,否则您将无法IDisposable
在需要时正确实现该模式。阅读这篇文章以获得关于该主题的非常有启发性的答案,以及Eric Lippert的一些笑声。
以下实现是否符合我的意图?
不,它没有,更糟糕的DocXManager
是,合同简直太可怕了(稍后会详细介绍)。
如果wordDoc.Save()
由于文件正在被另一个进程使用,或者您的硬盘驱动器空间不足,或者您失去连接等而引发抛出和异常,会发生什么?
如果抛出的异常是不可恢复的(它没有在你的代码中的任何地方处理,或者它是但你会尽快终止它)那么这不是一个真正的问题,运行时会在进程终止时清理你身后的所有东西。另一方面,如果处理了异常(警告用户文件正在使用,目录不可用等)并且进程继续运行,那么您可能只是泄漏了资源。
如何避免这种情况?使用try-finally
块:
public void makeChangesAndSaveDocX(DocX wordDoc)
{
try
{
wordDoc.Save();
}
finally
{
((IDisposable)wordDoc).Dispose();
}
}
现在您确定Dispose()
在任何可恢复的情况下都会调用它。
那么,这够好吗?嗯……不完全是。这里的问题是makeChangesAndSaveDocX
(MakeChangesAndSaveDocX
顺便说一句)合同不清楚。谁负责处置wordDoc
?MakeChangesAndSaveDocX
还是来电者?为什么选择其中一个?wordDoc
消费者如何知道一旦他被调用就不需要担心MakeChangesAndSaveDocX
?wordDoc
或者他怎么知道在调用公共方法后他不能使用MakeChangesAndSaveDocX
?为什么假设消费者调用后就DocXManager
不需要使用?哎呀……这真是一团糟。wordDoc
MakeChangesAndSaveDocX
我建议您重新考虑您的方法并执行以下操作之一:
- 如果方法签名
MakeChangesAndSaveDocX(DocX wordDoc)
有意义(其他人可以拥有 wordDoc
),则不要 wordDoc
在MakeChangesAndSaveDocX
. 让调用者承担那个负担,他应该负责,因为对象属于他而不是MakeChangesAndSaveDocX
。
另一方面,如果不DocXManager
拥有的其他人应该成为状态的一部分是没有意义的,wordDoc
那么您应该重新考虑以下几行的实现:wordDoc
DocXManager
DocXManager
public class DocXManager: IDisposable
{
private readonly DocX docX;
private bool disposed;
public DocXManager(string filePath)
{
docX = new DocX(filePath);
}
public void MakeChangesAndSaveDocX()
{
if (disposed)
throw new ObjectDisposedException();
docX.Save();
}
public void FrobDocX(Frobber frobber)
{
if (disposed)
throw new ObjectDisposedException();
frobber.Frob(docX);
}
public void Dispose()
{
if (disposed)
return;
Dispose(true);
disposed = true;
GC.SupressFinalize(this);
}
public void Dispose(bool disposing)
{
if (disposing)
{
//we can sefely dispose managed resources here
((IDisposable)docX).Dispose();
}
//unsafe to clean up managed resources here, only clean up unmanaged resources if any.
}
~DocXManager()
{
Dispose(false);
}
}
现在你有了一份明确的合同;DocManagerX
负责正确处理DocX
,消费者负责正确处理DocManagerX
他可能使用的任何实例。一旦职责明确,就更容易推断代码的正确性以及谁应该做什么。
您可以通过以下方式使用管理器:
using (var manager = new DocXManager(path))
{
manager.FrobDoxX(frobber);
manager.MakeChangesAndSaveDocX();
} //manager is guaranteed to be disposed at this point (ignoring scenarios where finally blocks are not executed; StackOverflowException, OutOfMemoryException, etc.)