【问题标题】:Dispose, when is it called?Dispose,什么时候调用?
【发布时间】:2010-05-20 07:53:28
【问题描述】:

考虑以下代码:

namespace DisposeTest
{
    using System;

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Calling Test");

            Test();

            Console.WriteLine("Call to Test done");
        }

        static void Test()
        {
            DisposeImplementation di = new DisposeImplementation();
        }
    }

    internal class DisposeImplementation : IDisposable
    {
        ~DisposeImplementation()
        {
            Console.WriteLine("~ in DisposeImplementation instance called");
        }
        public void Dispose()
        {
            Console.WriteLine("Dispose in DisposeImplementation instance called");
        }
    }
}

Dispose 永远不会被调用,即使我在 Test(); 调用之后放置了一个等待循环。所以这很糟糕。我想编写一个简单易用的类,以确保清理所有可能的资源。我不想把这个责任推给我班级的用户。

可能的解决方案:使用using,或者自己调用Dispose(基本一样)。我可以强制用户使用 using 吗?或者我可以强制调用 dispose 吗?

Test(); 之后调用GC.Collect(); 也不起作用。

di 放入null 也不会调用 Dispose。 Deconstructor 可以工作,所以对象在退出时会被解构Test()

好的,伙计们,现在很清楚了!

谢谢大家的回答!我将在评论中添加警告!

【问题讨论】:

    标签: c# .net garbage-collection dispose idisposable


    【解决方案1】:

    为了解决 OP 的问题,有几点很重要:

    1. .NET GC 是不确定的(即您永远不知道也不应该依赖它何时发生)
    2. .NET Framework 从不调用 Dispose;您必须手动调用它 - 最好将其创建包装在 using() 块中。
    3. 将一次性对象显式设置为 null 而不对其调用 Dispose() 是一件坏事。发生的情况是您将对象“根引用”显式设置为空。这实际上意味着您不能稍后调用 Dispose,更重要的是,它将对象发送到 GC 终结队列以进行终结。应不惜一切代价避免因不良编程实践而导致终结。

    终结器: 一些开发人员将其称为析构函数。 事实上,在C# 4.0 Language Spec (section 1.6.7.6) 和当前ECMA-334 spec以前的 版本中,它甚至被称为析构函数。幸运的是,第 4 版(2006 年 6 月)在第 8.7.9 节中正确定义了终结器,并试图在第 17.12 节中消除两者之间的混淆。应该注意的是,在 .NET Framework 中传统上称为析构函数和析构函数/终结器之间存在重要的内部差异(此处无需深入讨论这些血淋淋的细节)。

    1. 如果存在终结器,则当且仅当未调用 GC.SuppressFinalize() 时,.NET Framework 才会调用它。
    2. 永远不要显式调用终结器。幸运的是,C# 不会明确允许这样做(我不知道其他语言);虽然可以通过调用GC.Collect(2) 来强制执行第 2 代 GC。

    定稿: 终结是 .NET Framework 处理“优雅”清理和释放资源的方式。

    1. 只有在终结队列中有对象时才会发生。
    2. 只有在 Gen2 发生垃圾回收时才会发生这种情况(对于编写良好的 .NET 应用,每 100 次垃圾回收大约 1 次)。
    3. 在 .NET 4 之前(包括在内),只有一个终结线程。如果此线程因任何原因被阻止,您的应用程序就完蛋了。
    4. 编写正确且安全的终结代码并非易事,而且很容易出错(例如,意外地允许终结器抛出异常,允许依赖于可能已经终结的其他对象等)

    虽然这肯定是您要求的更多信息,但它提供了有关事物如何工作以及它们为何以这种方式工作的背景。有些人会争辩说他们不必担心在 .NET 中管理内存和资源,但这并不会改变需要完成的事实——而且我认为在不久的将来这种情况不会消失。

    不幸的是,上面的示例(错误地)暗示您需要实现终结器作为标准 Dispose 模式的一部分。 但是,除非您使用非托管代码,否则不应实现终结器。否则,会对性能产生负面影响。

    我在这里发布了一个实现 Dispose 模式的模板:How do you properly implement the IDisposable pattern?

    【讨论】:

    • "Dispose 永远不会被 .NET Framework 调用;您必须手动调用它" - using 语句在内部调用 Dispose
    • 是的,'using' 语句实际上是一个 try/finally,在 finally 子句中调用 Dispose。我的观点是,您实际上必须使用“using()”语句编写代码,或者自己调用 Dispose。换句话说,如果您不将一次性资源的使用包装在“using()”语句中,则可以泄漏资源和/或将其放入队列中以进行终结。
    • 是的,我的意思是该评论略微误导了读者,您可以更新为“.NET Framework 从不调用Dispose;您必须调用它手动或用using 语句包装一次性对象”或类似的东西......
    • 感谢您的澄清。我已经编辑了注释以包括使用“using()”块
    • 在这篇文章中,他们同意这里公开的大多数想法。它可以作为阅读更多相关信息的参考。 codeproject.com/articles/29534/…
    【解决方案2】:

    我想写一个类 简单易用, 确保一切可能 资源被清理。我不想 把责任交给用户 我班的。

    你不能那样做。内存管理根本不是为了容纳非专门的内存资源而构建的。

    IDisposable 模式旨在为开发人员提供一种告知对象何时完成的方式,而不是让内存管理尝试通过使用引用计数之类的东西来解决这个问题。

    您可以将终结器用作未能正确处置对象的用户的后备,但它不能很好地用作清理对象的主要方法。为了顺利地工作,对象应该被正确处理,这样更昂贵的终结器就不需要被调用了。

    【讨论】:

    • 但请注意不要调用终结器中的任何其他对象。终结的顺序是不确定的(特别是允许循环和其他交叉依赖),所以终结器唯一可以做的就是清理非托管资源。
    • 为什么投反对票?如果你不解释你认为错误的地方,它就无法改进答案。
    • 我不认为 Cygon 在这方面是绝对正确的,正如 MSDN 所说:“不要访问终结器代码路径中的任何可终结对象,因为它们已经存在很大风险定案。”请注意,它说的是 FINALIZABLE,而不是 ANY。并非每个对象都可以通过设计完成。 (参考:msdn.microsoft.com/en-us/library/b1yfkh5e(v=vs.110).aspx
    • @Califf:是的,这是正确的。但是,您仍然应该小心从终结器中引用的内容,因为即使在应用程序本身结束之后也可以执行它,并且系统只是将所有内容包装起来。
    • 我认为说“无能的开发人员”对有问题的人来说可能有点困难。
    【解决方案3】:

    所有答案(或多或少)都是正确的,这是一个例子:

    static void Test()
    {
        using (DisposeImplementation di = new DisposeImplementation())
        {
            // Do stuff with di
        }
    }
    

    手动调用Dispose也可以,但是using语句的优点是当你离开控制块时对象也会被释放,因为抛出了异常。

    您可以添加一个终结器来处理资源处置,以防有人“忘记”使用 IDisposable 接口:

    public class DisposeImplementation : IDisposable
    {    
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
    
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                // get rid of managed resources
            }   
            // get rid of unmanaged resources
        }
    
        ~DisposeImplementation()
        {
            Dispose(false);
        }
    }
    

    请参阅this question 了解更多信息。但是,这只是对没有正确使用您的类的人的补偿 :) 我建议您向 Finalizer 添加一个大胖子 Debug.Fail() 调用,以警告开发人员他们的错误。

    如果您选择实施该模式,您会看到GC.Collect() 将触发处置。

    【讨论】:

      【解决方案4】:

      将此用作您的课程的模式/模板

      public class MyClass : IDisposable
      {
          private bool disposed = false;
      
          // 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.
          private 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.                
                      ......
                  }
      
                  // Call the appropriate methods to clean up
                  // unmanaged resources here.
                  // If disposing is false,
                  // only the following code is executed.
                  ...........................
      
                  // Note disposing has been done.
                  disposed = true;
              }
          }
      
          // 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.
          ~MyClass()
          {
              // Do not re-create Dispose clean-up code here.
              // Calling Dispose(false) is optimal in terms of
              // readability and maintainability.
              Dispose(false);
          }
      }
      

      当然,正如其他人所提到的,不要忘记 using(...){} 块。

      【讨论】:

      • 你忘了提到永远不要实现终结器,除非你正在使用 非托管 资源。
      • 虽然如果你不需要终结器,那么整个 SuppressFinalize / 处置模式是多余的。
      • @yoyo - 绝对不是真的。仅仅因为您不需要终结器并不意味着您不应该实现 IDisposable。终结器(以及 Dispose 模式)对于清理/处置非托管资源是必需的。每当您需要清理托管资源时,都应实施 Dispose 模式/实现。
      • "// 不要在派生自此类的类型中提供析构函数。" ???如果他们也有需要处理的资源,我认为你可以。我阅读的方式是提供一个 ~DerivedClass() { Dispose(false); } 和一个受保护的覆盖 void Dispose(bool disposing) { ... base.Dispose(disposing); }
      【解决方案5】:

      您必须显式调用Dispose 或将对象包装在using 语句中。示例:

      using (var di = new DisposeImplementation())
      {
      }
      

      可能的解决方案:使用 using 或 call 处理自己(基本一样)。

      使用using 与在finally 块内调用Dispose 相同。

      【讨论】:

      • 唯一不应该使用“using()”语句的情况是在 WCF 客户端/代理的情况下。我的博客上有一篇文章解释了原因并提供了处理 WCF 客户端/代理的解决方案。 dave-black.blogspot.com/2012/03/…
      【解决方案6】:

      Dispose 不会被自动调用。您需要使用using 子句来包装用法或手动调用它。

      http://msdn.microsoft.com/en-us/library/aa664736%28VS.71%29.aspx

      为了抢占你可能有的另一个想法:你不能从析构函数中调用dispose...我前段时间在一个项目中尝试过。

      【讨论】:

      • C# 中不存在析构函数。他们被称为终结者。您实际上可以从终结器中调用它(请参阅我的回答),但您可能需要采取特殊的预防措施。
      • C# 确实有析构函数:参见语言规范或msdn.microsoft.com/en-us/library/66x5fx1b.aspx
      • @RodrickChapman:请参阅 Dave Black 的回答。它们使用与 C++ 析构函数相同的 ~ClassName 命名,但它们不是析构函数,它们是终结器。 C# 编译器采用所谓的但不是实际的析构函数中的代码,并使用它来实现Object.Finalize() 的重载(它也会自动添加一些东西)
      【解决方案7】:

      您应该自己处理它,或者调用Dispose 方法或使用using。请记住,它不是解构器!

      如果您不能信任您班级的用户能够正确地处理资源,那么他们可能会以其他方式搞砸。

      【讨论】:

      • “人们在尝试设计完全万无一失的东西时常犯的错误是低估了完全傻瓜的独创性。” —— 道格拉斯·亚当斯,基本无害
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-12-25
      • 1970-01-01
      • 2014-03-27
      • 2010-10-18
      • 2016-03-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多