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)的合同不明确。谁负责处置wordDocMakeChangesAndSaveDocX 还是来电者?为什么一个或另一个?消费者如何知道在调用 MakeChangesAndSaveDocX 后他不需要担心 wordDoc?或者他怎么知道在调用了public方法MakeChangesAndSaveDocX之后他不能使用wordDoc?为什么 DocXManager 假设消费者在调用 MakeChangesAndSaveDocX 后不需要使用 wordDoc?呸……一团糟。

我建议您重新考虑您的方法并执行以下操作之一:

  1. 如果方法签名MakeChangesAndSaveDocX(DocX wordDoc)有意义(其他人可以拥有wordDoc),那么不要处理 wordDocMakeChangesAndSaveDocX。让调用者承担那个负担,他应该负责,因为该对象属于而不属于MakeChangesAndSaveDocX.
  2. 如果另一方面,不是 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.)