【问题标题】:How to implement IDisposable properly如何正确实施 IDisposable
【发布时间】:2010-04-29 10:10:48
【问题描述】:

在我作为开发人员的那段时间里,我见过很多 C# 代码,它们试图通过将变量设置为 null 或在他们自己的类 Dispose() 方法中调用类(例如 DataSet)上的 Dispose() 来帮助 GC 我一直想知道是否有必要在托管环境中实现它。

这段代码在其设计模式中是否浪费时间?

class MyClass : IDisposable 
{
    #region IDisposable Members

    public void Dispose() 
    {
        otherVariable = null;
        if (dataSet != null)
        {
            dataSet.Dispose();
        }
    }

    #endregion
}

【问题讨论】:

    标签: c# idisposable


    【解决方案1】:

    GC 调用.Dispose()(但是,它调用了finalize ~MyClass() 方法,您可以提供对Dispose() 方法的调用来自动管理资源GC 决定清理你的类)。

    您必须始终提供一种将内部资源(例如DataSets)分配给使用您的类的代码的方法(并确保您实际调用.Dispose() 或将构造函数包装在using 中)。强烈建议在使用内部资源的类上使用 IDisposable

    来自MSDN

    这个接口的主要用途是 释放非托管资源。这 垃圾收集器自动 释放分配给 a 的内存 当该对象为 no 时的托管对象 使用时间更长。然而,它不是 可以预测什么时候垃圾 将发生收集。此外, 垃圾收集器没有知识 非托管资源,例如窗口 句柄,或打开文件和流。

    public void Dispose()
    {
        otherVariable = null;
        if (dataSet != null)
        {
            dataSet.Dispose();
            dataSet = null;
        }
    }
    

    【讨论】:

      【解决方案2】:

      不,Dispose 方法不会浪费时间。

      dispose 模式允许调用者在完成一个类后立即清理它,而不是等待 GC 收集它。延迟对于普通堆内存来说并不重要,这就是为什么像String 这样的基本类不实现它的原因。然而,Dispose 有用的是清理非托管资源。在内部某处,Dataset 类正在使用非托管资源,因此它提供了一个 dispose 方法,让您可以让它知道何时可以释放该非托管资源。

      如果正确地遵循了模式,Dataset 也将有一个终结器(或某些子类),这意味着如果您没有手动处理它,最终 GC 将运行,终结器将被调用并且非托管资源会这样清理。不过,这个非托管资源可能很重要,想象一下,如果它是文件锁或数据库连接,您真的不想在重用数据库连接之前等待 GC 运行。 Dispose 提供了一种确定性的方式来在资源完成时清理它们,而不是依赖于非确定性的 GC。

      至于在 dispose 方法中将变量设置为 null。这几乎在所有情况下都是毫无意义的。将变量设置为 null 会删除对该变量的引用,这将使其有资格进行垃圾收集(如果这是最后一个引用),但是当您无论如何都要处理该类时,您可能会超出包含的范围类,因此内部类无论如何都会有资格被收集。

      如果你的类中有你创建的一次性成员变量(不仅仅是你持有的引用),那么你应该总是从你自己的类的 dispose 方法中调用它们的 dispose,但不要费心将它们设置为 null。

      【讨论】:

        【解决方案3】:

        不完全是。如果您有一次性的成员变量,那么您可能应该像这样处理它。您的对象的寿命可能比它正在执行的工作范围更长,因为不能保证垃圾收集器在任何特定时间运行。

        不过,将托管变量设置为 null 是浪费时间。该对象不会更快地被 GC。

        【讨论】:

          【解决方案4】:

          垃圾车每周都会来到我的区域,但它不会收集我的垃圾,除非我把垃圾桶放在它可以收集的地方。

          您应该简单地删除所有不需要的事件订阅、引用和清除非托管处理程序。然后垃圾收集器会处理剩下的事情。

          以下示例显示了实现 IDisposable 接口的一般最佳实践。参考:https://msdn.microsoft.com/en-us/library/system.idisposable.dispose(v=vs.110).aspx

          public class DisposeExample
          {
              // A base class that implements IDisposable. 
              // By implementing IDisposable, you are announcing that 
              // instances of this type allocate scarce resources. 
              public class MyResource: IDisposable
              {
                  // Pointer to an external unmanaged resource. 
                  private IntPtr handle;
                  // Other managed resource this class uses. 
                  private Component component = new Component();
                  // Track whether Dispose has been called. 
                  private bool disposed = false;
          
                  // The class constructor. 
                  public MyResource(IntPtr handle)
                  {
                      this.handle = handle;
                  }
          
                  // Implement IDisposable. 
                  // Do not make this method virtual. 
                  // A derived class should not be able to override this method. 
                  public void Dispose()
                  {
                      Dispose(true);
                      // This object will be cleaned up by the Dispose method. 
                      // Therefore, you should call GC.SupressFinalize to 
                      // take this object off the finalization queue 
                      // and prevent finalization code for this object 
                      // from executing a second time.
                      GC.SuppressFinalize(this);
                  }
          
                  // Dispose(bool disposing) executes in two distinct scenarios. 
                  // If disposing equals true, the method has been called directly 
                  // or indirectly by a user's code. Managed and unmanaged resources 
                  // can be disposed. 
                  // If disposing equals false, the method has been called by the 
                  // runtime from inside the finalizer and you should not reference 
                  // other objects. Only unmanaged resources can be disposed. 
                  protected virtual void Dispose(bool disposing)
                  {
                      // Check to see if Dispose has already been called. 
                      if(!this.disposed)
                      {
                          // If disposing equals true, dispose all managed 
                          // and unmanaged resources. 
                          if(disposing)
                          {
                              // Dispose managed resources.
                              component.Dispose();
                          }
          
                          // Call the appropriate methods to clean up 
                          // unmanaged resources here. 
                          // If disposing is false, 
                          // only the following code is executed.
                          CloseHandle(handle);
                          handle = IntPtr.Zero;
          
                          // Note disposing has been done.
                          disposed = true;
          
                      }
                  }
          
                  // Use interop to call the method necessary 
                  // to clean up the unmanaged resource.
                  [System.Runtime.InteropServices.DllImport("Kernel32")]
                  private extern static Boolean CloseHandle(IntPtr handle);
          
                  // Use C# destructor syntax for finalization code. 
                  // This destructor will run only if the Dispose method 
                  // does not get called. 
                  // It gives your base class the opportunity to finalize. 
                  // Do not provide destructors in types derived from this class.
                  ~MyResource()
                  {
                      // Do not re-create Dispose clean-up code here. 
                      // Calling Dispose(false) is optimal in terms of 
                      // readability and maintainability.
                      Dispose(false);
                  }
              }
              public static void Main()
              {
                  // Insert code here to create 
                  // and use the MyResource object.
              }
          }
          

          【讨论】:

          • IDisposable 的目的不是破坏资源,而是释放资源。当FileStream 打开文件时,它会请求操作系统阻止其他人使用该文件,直至另行通知;在FileStream 上调用Dispose 不会破坏文件——相反,它使其可供其他实体使用。 GC 运行的原则是,当有空间容纳它们时,不应该急于销毁它们,但是一旦代码不再使用 FileStream,它应该尽快尝试使文件再次可供其他代码使用可能。
          • @supercat:不太清楚你想说什么。我猜 FileStream Dispose 确实释放了文件处理程序并让垃圾收集器清除托管实例。如果不是为了让其他人可以使用它的唯一目的。如果您不以独占方式打开文件,它可能仍然可以被其他人使用。
          • 单独的 GC 不足以处理 FileStream 之类的事情的原因并不是因为它们总是以一种干扰其他需要使用该文件,但他们有时会这样做,并且通常更容易在不再需要资源时释放资源,而不考虑是否实际存在争用,而不是确定不会出现争用的情况在这种情况下存在并放弃资源而不释放它们。请注意,sometimes 不一定要非常“经常”才能使IDisposable 有价值。即使放弃文件流...
          • ...在 99% 的时间里都可以工作,在 100% 的情况下不再需要对文件流进行代码调用 Dispose 可能比编写通常的代码更容易不会 100% 处理它们。
          猜你喜欢
          • 2013-08-22
          • 1970-01-01
          • 2014-07-01
          • 2019-02-01
          • 2022-12-22
          • 1970-01-01
          • 2020-11-22
          • 2010-11-16
          • 2011-01-26
          相关资源
          最近更新 更多