处理,何时被称为?

Sna*_*ake 29 .net c# garbage-collection dispose idisposable

请考虑以下代码:

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");
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

呼叫GC.Collect();Test();也不起作用.

dinull不调用任何处置.解构器可以工作,因此对象在退出时会被解构Test()

好的,现在很清楚!

谢谢大家的答案!我会在评论中添加警告!

Dav*_*ack 48

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

  1. .NET GC是非确定性的(即你永远不知道也不应该依赖于它何时发生)
  2. .NET Framework永远不会调用Dispose; 你必须手动调用它 - 最好是将它的创建包装在一个using()块中.
  3. 在不调用Dispose()的情况下将一次性对象显式设置为null是一件坏事.发生的是您将对象"root reference"显式设置为null.这实际上意味着您以后不能调用Dispose,更重要的是,它将对象发送到GC Finalization Queue for Finalization.应该不惜一切代价避免因糟糕的编程习惯而导致终结.

终结器: 一些开发人员将其称为析构函数.事实上,它甚至在C#4.0语言规范(第1.6.7.6节)和当前ECMA-334规范的先前版本中称为析构函数.幸运的是,第4版(2006年6月)正确定义了第8.7.9节中的终结器,并尝试在第17.12节中澄清两者之间的混淆.应该注意的是,在.NET Framework中传统上称为析构函数和析构函数/终结符之间存在重要的内部差异(不需要在这里进行详细介绍).

  1. 如果存在Finalizer,那么当且仅当GC.SuppressFinalize()未调用时,.NET Framework将调用它.
  2. 你永远不应该明确地调用终结器.幸运的是,C#不会明确允许这个(我不知道其他语言); 虽然可以通过呼吁GC.Collect(2)第二代GC 来强制它.

完成: 最终化是.NET Framework处理"优雅"清理和释放资源的方法.

  1. 只有在Finalization Queue中有对象时才会发生.
  2. 它只发生在Gen2的垃圾收集时(对于编写良好的.NET应用程序,每100个集合中大约有 1个).
  3. 除了.NET 4之外,还有一个Finalization线程.如果此线程因任何原因而被阻止,则您的应用程序会被屏蔽.
  4. 写适当和安全的终止代码是不平凡的和错误可以很容易地进行(即意外例外允许从终结被抛出,从而对其他对象的依赖可能已经敲定,等等)

虽然这肯定是您要求的更多信息,但它提供了有关工作方式以及工作原理的背景知识.有些人会争辩说他们不应该担心在.NET中管理内存和资源,但这并没有改变它需要完成的事实 - 我不认为它会在不久的将来消失.

  • 是的,'using'语句实际上是一个try/finally,在finally子句中调用Dispose.我的观点是你必须实际*用'using()'语句编写代码或调用Dispose自己.换句话说,如果您不在"using()"语句中包装使用可处理资源,则可以泄漏资源和/或将其放入队列中以进行Finalization. (7认同)
  • 是的,我的观点是评论对读者来说有点*误导,你可以更新说"*.NET Framework永远不会调用Dispose;你必须手动调用它**或者使用`using`语句包装一次性对象***" 或类似的规定... (2认同)

Guf*_*ffa 21

我想编写一个简单易用的类,以确保清理所有可能的资源.我不想把这个责任交给我班级的用户.

你不能这样做.内存管理根本不是为了容纳非特定内存的资源而构建的.

IDisposable模式旨在让开发人员在完成对象时告诉对象,而不是让内存管理试图通过使用引用计数之类的东西来解决这个问题.

您可以将Finalizer用作未能正确处理对象的用户的后备,但它不能作为清理对象的主要方法.为了顺利工作,应妥善处理对象,以便不需要调用更昂贵的终结器.

  • 为什么选择downvote?如果你不解释你认为错误的东西,它就无法改善答案. (7认同)
  • 但请注意不要在终结器中调用任何其他对象.最终定义的顺序是未定义的(特别是在允许循环和其他交叉依赖性的情况下),因此允许终结器唯一能够清理非托管资源. (3认同)

Tho*_*rin 13

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

static void Test()
{
    using (DisposeImplementation di = new DisposeImplementation())
    {
        // Do stuff with di
    }
}
Run Code Online (Sandbox Code Playgroud)

手动调用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);
    }
}
Run Code Online (Sandbox Code Playgroud)

有关其他信息,请参阅此问题.但是,这只是补偿了没有正确使用你的课程的人:)我建议你Debug.Fail()给Finalizer 添加一个很大的调用,以警告开发人员他们的错误.

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


Inc*_*ito 7

将其用作类的模式/模板

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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