1

我想要一个方法来返回一个非托管资源,然后在程序中处理该资源。以下实现是否符合我的意图?

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();
    }
}
4

1 回答 1

2

首先,您似乎误解了托管和非托管资源的概念。wordDoc不是托管资源,它是托管资源,它恰好直接持有非托管资源或充当其他IDisposable对象的包装器(您不在乎两者中的哪一个是真的)。清楚这一点很重要,否则您将无法IDisposable在需要时正确实现该模式。阅读这篇文章以获得关于该主题的非常有启发性的答案,以及Eric Lippert的一些笑声。

以下实现是否符合我的意图?

不,它没有,更糟糕的DocXManager是,合同简直太可怕了(稍后会详细介绍)。

如果wordDoc.Save()由于文件正在被另一个进程使用,或者您的硬盘驱动器空间不足,或者您失去连接等而引发抛出和异常,会发生什么?

如果抛出的异常是不可恢复的(它没有在你的代码中的任何地方处理,或者它是但你会尽快终止它)那么这不是一个真正的问题,运行时会在进程终止时清理你身后的所有东西。另一方面,如果处理了异常(警告用户文件正在使用,目录不可用等)并且进程继续运行,那么您可能只是泄漏了资源。

如何避免这种情况?使用try-finally块:

public void makeChangesAndSaveDocX(DocX wordDoc)
{
   try
   {
       wordDoc.Save();
   }
   finally
   {
      ((IDisposable)wordDoc).Dispose();
   }
} 

现在您确定Dispose()在任何可恢复的情况下都会调用它。

那么,这够好吗?嗯……不完全是。这里的问题是makeChangesAndSaveDocXMakeChangesAndSaveDocX顺便说一句)合同不清楚。谁负责处置wordDocMakeChangesAndSaveDocX还是来电者?为什么选择其中一个?wordDoc消费者如何知道一旦他被调用就不需要担心MakeChangesAndSaveDocXwordDoc或者他怎么知道在调用公共方法后他不能使用MakeChangesAndSaveDocX?为什么假设消费者调用后就DocXManager不需要使用?哎呀……这真是一团糟。wordDocMakeChangesAndSaveDocX

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

  1. 如果方法签名MakeChangesAndSaveDocX(DocX wordDoc)有意义(其他人可以拥有 wordDoc),则不要 wordDocMakeChangesAndSaveDocX. 让调用者承担那个负担,他应该负责,因为对象属于而不是MakeChangesAndSaveDocX
  2. 另一方面,如果不DocXManager拥有的其他人应该成为状态的一部分是没有意义的,wordDoc那么您应该重新考虑以下几行的实现:wordDocDocXManagerDocXManager

     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.)
于 2016-05-19T10:14:09.387 回答