代码之家  ›  专栏  ›  技术社区  ›  Anemoia

Dispose,何时调用?

  •  26
  • Anemoia  · 技术社区  · 16 年前

    考虑以下代码:

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

    即使我在 Test(); 调用。所以这很糟糕。我想编写一个简单易用的类,以确保清除所有可能的资源。我不想把这种责任推给我班的用户。

    可能的解决方案:使用 using 或者叫自己处理(基本相同)。我可以强制用户使用吗?或者我可以强制调用Dispose吗?

    打电话 GC.Collect(); 之后 测试(); 也不起作用。

    di null 也不调用Dispose。解构器确实有效,因此对象get在退出时解构 Test()

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

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

    7 回复  |  直到 7 年前
        1
  •  18
  •   morhook Amber    7 年前

    我想写一个 简单易用, 确保一切可能 资源清理完毕。我不想要 对用户负责 我的班级。

    你不能那样做。内存管理并不是专门为适应非内存资源而构建的。

    IDisposable模式是为开发人员设计的一种方法,用于告诉对象什么时候完成了它,而不是让内存管理人员试图通过使用引用计数之类的方法来解决这个问题。

    对于无法正确处理对象的用户,可以使用终结器作为回退,但它不如用于清理对象的主要方法工作得好。为了顺利工作,应该正确地处理对象,这样就不需要调用成本更高的终结器。

        2
  •  46
  •   morhook Amber    8 年前

    应提出几个要点来解决OP的问题:

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

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

    1. 如果存在终结器,则.NET框架将仅当且仅当 GC.SuppressFinalize() 不被调用。
    2. 不应显式调用终结器。幸运的是,C不会明确允许这样做(我不知道其他语言);尽管它可以通过调用 GC.Collect(2) 用于第二代GC。

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

    1. 仅当终结队列中存在对象时才会发生。
    2. 它只在gen2发生垃圾收集时发生(即 大约 对于一个写得很好的.NET应用程序,每100个集合中就有1个)。
    3. 在.NET 4之前(包括.NET 4),只有一个终结线程。如果这个线程由于任何原因被阻塞,你的应用程序就会被拧紧。
    4. 编写正确和安全的定稿代码是非常重要的,并且可以很容易地犯错误(即意外地允许从定稿器中抛出异常,允许依赖于可能已经定稿的其他对象等)。

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

        3
  •  13
  •   Community Mohan Dere    9 年前

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

    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 更多信息。但是,这只是对那些没有正确使用你的类的人的补偿:)我建议你增加一个大脂肪 Debug.Fail() 调用终结器,警告开发人员他们的错误。

    如果您选择实现模式,您将看到 GC.Collect() 将触发处理。

        4
  •  7
  •   htellez    12 年前

    将其用作类的模式/模板

    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(...){} 块。

        5
  •  2
  •   Anne Sharp    16 年前

    你得打电话 Dispose 显式或通过将对象包装在 using 语句。例子:

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

    可能的解决方案:使用或调用 处理好自己(基本相同)。

    使用 使用 和打电话一样吗 处置 里面 finally 块。

        6
  •  1
  •   Hans Olsson    16 年前

    你应该自己处理它,或者打电话给 Dispose 方法或使用 using . 记住,这不是解构主义者!

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

        7
  •  1
  •   morhook Amber    8 年前

    不会自动调用Dispose。你需要使用 using 子句包装使用或手动调用它。

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

    为了先发制人,你可能有另一个想法:你不能打电话给 dispose 从析构函数…我以前在一个项目中试过这个。