C# 在 "using" 和 "try/finalize" 块之外处理非托管资源
C# dispose unmanaged resource outside "using" and "try/finalize" block
我想要一种方法 return 非托管资源,稍后在程序中处理该资源。以下实现是否符合我的意图?
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();
}
}
首先,您似乎误解了托管资源和非托管资源的概念。 wordDoc
不是 非托管资源,它是 托管资源 恰好直接持有 非托管资源 或充当其他 IDisposable
对象的包装器(您不关心两者中的哪一个是真的)。清楚这一点很重要,否则您将无法在需要时正确实施 IDisposable
模式。阅读 for a very instructive answer on the subject and a few chuckles courtesy of Eric Lippert.
Does the following implementation do what I intend ?
不,它没有,更糟糕的是,DocXManager
的合同简直太可怕了(稍后会详细介绍)。
如果 wordDoc.Save()
抛出异常会发生什么,因为该文件正被另一个进程使用,或者您可能 运行 超出 space 硬盘驱动器,或者您失去联系等?
如果抛出的异常是不可恢复的(它没有在你的代码中的任何地方处理,或者它是但你会尽快终止它)那么它不是真正的问题,运行时间会清理你身后的一切当进程终止时。另一方面,如果处理了异常(警告用户文件正在使用,目录不可用等)并且进程保持 运行ning 那么你可能只是泄漏了资源。
如何避免这种情况?使用 try-finally
块:
public void makeChangesAndSaveDocX(DocX wordDoc)
{
try
{
wordDoc.Save();
}
finally
{
((IDisposable)wordDoc).Dispose();
}
}
现在您确定 Dispose()
将在任何可恢复的情况下被调用。
那么,这够好了吗?好吧……不完全是。这里的问题是makeChangesAndSaveDocX
的(顺便说一下应该是MakeChangesAndSaveDocX
)的合同不明确。谁负责处置wordDoc
? MakeChangesAndSaveDocX
还是来电者?为什么一个或另一个?消费者如何知道在调用 MakeChangesAndSaveDocX
后他不需要担心 wordDoc
?或者他怎么知道在调用了public方法MakeChangesAndSaveDocX
之后他不能使用wordDoc
?为什么 DocXManager
假设消费者在调用 MakeChangesAndSaveDocX
后不需要使用 wordDoc
?呸……一团糟。
我建议您重新考虑您的方法并执行以下操作之一:
- 如果方法签名
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; WhosebugException, OutOfMemoryException, etc.)
我想要一种方法 return 非托管资源,稍后在程序中处理该资源。以下实现是否符合我的意图?
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();
}
}
首先,您似乎误解了托管资源和非托管资源的概念。 wordDoc
不是 非托管资源,它是 托管资源 恰好直接持有 非托管资源 或充当其他 IDisposable
对象的包装器(您不关心两者中的哪一个是真的)。清楚这一点很重要,否则您将无法在需要时正确实施 IDisposable
模式。阅读
Does the following implementation do what I intend ?
不,它没有,更糟糕的是,DocXManager
的合同简直太可怕了(稍后会详细介绍)。
如果 wordDoc.Save()
抛出异常会发生什么,因为该文件正被另一个进程使用,或者您可能 运行 超出 space 硬盘驱动器,或者您失去联系等?
如果抛出的异常是不可恢复的(它没有在你的代码中的任何地方处理,或者它是但你会尽快终止它)那么它不是真正的问题,运行时间会清理你身后的一切当进程终止时。另一方面,如果处理了异常(警告用户文件正在使用,目录不可用等)并且进程保持 运行ning 那么你可能只是泄漏了资源。
如何避免这种情况?使用 try-finally
块:
public void makeChangesAndSaveDocX(DocX wordDoc)
{
try
{
wordDoc.Save();
}
finally
{
((IDisposable)wordDoc).Dispose();
}
}
现在您确定 Dispose()
将在任何可恢复的情况下被调用。
那么,这够好了吗?好吧……不完全是。这里的问题是makeChangesAndSaveDocX
的(顺便说一下应该是MakeChangesAndSaveDocX
)的合同不明确。谁负责处置wordDoc
? MakeChangesAndSaveDocX
还是来电者?为什么一个或另一个?消费者如何知道在调用 MakeChangesAndSaveDocX
后他不需要担心 wordDoc
?或者他怎么知道在调用了public方法MakeChangesAndSaveDocX
之后他不能使用wordDoc
?为什么 DocXManager
假设消费者在调用 MakeChangesAndSaveDocX
后不需要使用 wordDoc
?呸……一团糟。
我建议您重新考虑您的方法并执行以下操作之一:
- 如果方法签名
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; WhosebugException, OutOfMemoryException, etc.)