我想要一个方法来返回一个非托管资源,然后在程序中处理该资源。以下实现是否符合我的意图?
class DocxManager
{
// DocX is the unmanaged resource
public Docx createDocX(string pathToFile)
{
return new DocX(pathToFile);
}
public void makeChangesAndSaveDocX(DocX wordDoc)
{
wordDoc.Save();
// IS THIS WAY THE UNMANAGED RESOURCE FREED CORRECTLY AND NO MEMORY LEAK ?
((IDisposable)wordDoc).Dispose();
}
}
答案 0 :(得分:2)
首先,您似乎误解了托管和非托管资源的概念。 wordDoc
不是非托管资源,它是托管资源,它直接持有非托管资源或充当包装器围绕其他IDisposable
对象(你不关心这两个对象是否属实)。重要的是你清楚这一点,否则你将无法在需要时正确实现IDisposable
模式。请阅读this以获得有关此主题的非常有益的答案,并在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.)