C#在“using”和“try / finalize”块之外部署非托管资源

时间:2016-05-19 07:29:23

标签: c# unmanagedresources

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

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

1 个答案:

答案 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)合同不清楚。谁负责处置wordDocMakeChangesAndSaveDocX还是来电者?为什么选择其中一个?一旦他被称为wordDoc,消费者如何知道他并不需要担心MakeChangesAndSaveDocX?或者他怎么知道在调用公共方法wordDoc后他不能使用MakeChangesAndSaveDocX?为什么DocXManager假设消费者在调用wordDoc后不需要使用MakeChangesAndSaveDocX?哎呀......这太乱了。

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

  1. 如果方法签名MakeChangesAndSaveDocX(DocX wordDoc)有意义(其他人可以拥有 wordDoc),那么不会将 wordDoc置于其中MakeChangesAndSaveDocX。让呼叫者承担这个负担,他应该负责,因为该对象属于而不属于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);
         }
    } 
    
  3. 现在你已经有了明确的合同; 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.)