正确使用IDisposable接口

问题描述 投票:1512回答:19

我从阅读the Microsoft documentation得知,IDisposable界面的“主要”用途是清理非托管资源。

对我来说,“非托管”意味着数据库连接,套接字,窗口句柄等等。但是,我已经看到了实现Dispose()方法以释放托管资源的代码,这对我来说似乎是多余的,因为垃圾收集器应该小心这对你来说。

例如:

public class MyCollection : IDisposable
{
    private List<String> _theList = new List<String>();
    private Dictionary<String, Point> _theDict = new Dictionary<String, Point>();

    // Die, clear it up! (free unmanaged resources)
    public void Dispose()
    {
        _theList.clear();
        _theDict.clear();
        _theList = null;
        _theDict = null;
    }

我的问题是,这是否使MyCollection使用的垃圾收集器空闲内存比通常更快?

编辑:到目前为止,人们已经发布了一些使用IDisposable清理非托管资源(例如数据库连接和位图)的好例子。但是假设上面代码中的_theList包含一百万个字符串,并且你想现在释放那个内存,而不是等待垃圾收集器。上面的代码会实现吗?

c# .net garbage-collection idisposable
19个回答
2453
投票

Dispose的目的是释放非托管资源。它需要在某个时刻完成,否则它们将永远不会被清除。垃圾收集器不知道如何在DeleteHandle()类型的变量上调用IntPtr,它不知道它是否需要调用DeleteHandle()

注意:什么是非托管资源?如果您在Microsoft .NET Framework中找到它:它是受管理的。如果你自己去探索MSDN,它是不受管理的。你使用P / Invoke调用的任何东西都是在.NET Framework中可以获得的所有可用的美好世界之外的东西是不受管理的 - 你现在负责清理它。

您创建的对象需要公开一些外部世界可以调用的方法,以便清理非托管资源。该方法可以任意命名:

public void Cleanup()

要么

public void Shutdown()

但相反,此方法有一个标准化名称:

public void Dispose()

甚至还创建了一个界面IDisposable,它只有一个方法:

public interface IDisposable
{
   void Dispose()
}

因此,您使对象公开IDisposable接口,这样您就可以保证已经编写了单一方法来清理非托管资源:

public void Dispose()
{
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
}

而且你已经完成了。除了你可以做得更好。


如果你的对象已经将250MB System.Drawing.Bitmap(即.NET托管的Bitmap类)分配为某种帧缓冲区怎么办?当然,这是一个托管的.NET对象,垃圾收集器将释放它。但是你真的想留下250MB的内存 - 等待垃圾收集器最终出现并释放它吗?如果有open database connection怎么办?当然,我们不希望该连接处于打开状态,等待GC完成对象。

如果用户调用Dispose()(意味着他们不再计划使用该对象),为什么不摆脱那些浪费的位图和数据库连接?

所以现在我们将:

  • 摆脱非托管资源(因为我们必须),和
  • 摆脱托管资源(因为我们想要帮助)

所以让我们更新我们的Dispose()方法来摆脱那些托管对象:

public void Dispose()
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);

   //Free managed resources too
   if (this.databaseConnection != null)
   {
      this.databaseConnection.Dispose();
      this.databaseConnection = null;
   }
   if (this.frameBufferImage != null)
   {
      this.frameBufferImage.Dispose();
      this.frameBufferImage = null;
   }
}

一切都很好,除了你可以做得更好!


如果这个人忘了在你的物体上打电话给Dispose()怎么办?然后他们会泄漏一些非托管资源!

注意:它们不会泄漏托管资源,因为最终垃圾收集器将在后台线程上运行,并释放与任何未使用对象关联的内存。这将包括您的对象以及您使用的任何托管对象(例如BitmapDbConnection)。

如果这个人忘了打电话给Dispose(),我们仍然可以保存他们的培根!我们仍然有办法为它们调用它:当垃圾收集器最终绕过来释放(即完成)我们的对象。

注意:垃圾收集器最终将释放所有托管对象。当它这样做时,它会在对象上调用Finalize方法。 GC不了解或关心您的Dispose方法。这只是我们选择的一个名称,当我们想要摆脱不受管理的东西时,我们会调用这个名称。

垃圾收集器破坏我们的对象是释放那些讨厌的非托管资源的最佳时机。我们通过覆盖Finalize()方法来做到这一点。

注意:在C#中,您没有显式覆盖Finalize()方法。您编写了一个看起来像C ++析构函数的方法,编译器将其作为Finalize()方法的实现:

~MyObject()
{
    //we're being finalized (i.e. destroyed), call Dispose in case the user forgot to
    Dispose(); //<--Warning: subtle bug! Keep reading!
}

但是该代码中存在一个错误。你看,垃圾收集器在后台线程上运行;你不知道两个对象被销毁的顺序。完全有可能在你的Dispose()代码中,你试图摆脱的托管对象(因为你想要有所帮助)不再存在:

public void Dispose()
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.gdiCursorBitmapStreamFileHandle);

   //Free managed resources too
   if (this.databaseConnection != null)
   {
      this.databaseConnection.Dispose(); //<-- crash, GC already destroyed it
      this.databaseConnection = null;
   }
   if (this.frameBufferImage != null)
   {
      this.frameBufferImage.Dispose(); //<-- crash, GC already destroyed it
      this.frameBufferImage = null;
   }
}

所以你需要的是Finalize()告诉Dispose()它不应该触及任何托管资源(因为它们可能不再存在),同时仍然释放非托管资源。

执行此操作的标准模式是让Finalize()Dispose()都调用第三个(!)方法;如果您从Dispose()(而不是Finalize())调用它,则传递布尔说法,这意味着释放托管资源是安全的。

这个内部方法可以给出一些任意的名称,如“CoreDispose”或“MyInternalDispose”,但传统上称之为Dispose(Boolean)

protected void Dispose(Boolean disposing)

但更有用的参数名称可能是:

protected void Dispose(Boolean itIsSafeToAlsoFreeManagedObjects)
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);

   //Free managed resources too, but only if I'm being called from Dispose
   //(If I'm being called from Finalize then the objects might not exist
   //anymore
   if (itIsSafeToAlsoFreeManagedObjects)  
   {    
      if (this.databaseConnection != null)
      {
         this.databaseConnection.Dispose();
         this.databaseConnection = null;
      }
      if (this.frameBufferImage != null)
      {
         this.frameBufferImage.Dispose();
         this.frameBufferImage = null;
      }
   }
}

并且您将IDisposable.Dispose()方法的实现更改为:

public void Dispose()
{
   Dispose(true); //I am calling you from Dispose, it's safe
}

你的终结者:

~MyObject()
{
   Dispose(false); //I am *not* calling you from Dispose, it's *not* safe
}

注意:如果您的对象来自实现Dispose的对象,那么在覆盖Dispose时不要忘记调用它们的基本Dispose方法:

public override void Dispose()
{
    try
    {
        Dispose(true); //true: safe to free managed resources
    }
    finally
    {
        base.Dispose();
    }
}

一切都很好,除了你可以做得更好!


如果用户在您的对象上调用Dispose(),则所有内容都已清除。稍后,当垃圾收集器出现并调用Finalize时,它将再次调用Dispose

这不仅浪费,而且如果你的对象有垃圾引用你最后一次调用Dispose()已经处理掉的对象,你会尝试再次处理它们!

您会注意到我的代码中我小心地删除了对已经处理的对象的引用,因此我不会尝试在垃圾对象引用上调用Dispose。但这并没有阻止一个微妙的错误蔓延。

当用户调用Dispose()时:句柄CursorFileBitmapIconServiceHandle被销毁。稍后当垃圾收集器运行时,它将尝试再次销毁相同的句柄。

protected void Dispose(Boolean iAmBeingCalledFromDisposeAndNotFinalize)
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle); //<--double destroy 
   ...
}

解决这个问题的方法是告诉垃圾收集器它不需要打扰最终确定对象 - 它的资源已经被清理掉了,不需要再做任何工作了。你可以通过在GC.SuppressFinalize()方法中调用Dispose()来做到这一点:

public void Dispose()
{
   Dispose(true); //I am calling you from Dispose, it's safe
   GC.SuppressFinalize(this); //Hey, GC: don't bother calling finalize later
}

现在用户已经调用了Dispose(),我们有:

  • 释放了非托管资源
  • 释放了托管资源

GC运行终结器没有意义 - 一切都在处理。

我不能使用Finalize来清理非托管资源吗?

Object.Finalize的文档说:

Finalize方法用于在销毁对象之前对当前对象持有的非托管资源执行清理操作。

但MSDN文档也说,对于IDisposable.Dispose

执行与释放,释放或重置非托管资源相关的应用程序定义的任务。

那是哪个呢?哪一个是我清理非托管资源的地方?答案是:

这是你的选择!但是选择Dispose

你当然可以将你的非托管清理放在终结器中:

~MyObject()
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);

   //A C# destructor automatically calls the destructor of its base class.
}

问题是你不知道什么时候垃圾收集器会来完成你的对象。您的未管理,不需要,未使用的本机资源将一直存在,直到垃圾收集器最终运行。然后它会调用你的终结者方法;清理非托管资源。 Object.Finalize的文档指出了这一点:

终结器执行的确切时间未定义。要确保为类的实例确定性地释放资源,请实现Close方法或提供IDisposable.Dispose实现。

这是使用Dispose清理非托管资源的优点;当清理非托管资源时,您将了解并控制。他们的破坏是“确定性的”。


回答你原来的问题:为什么不现在释放内存,而不是GC决定这样做?我有一个面部识别软件,现在需要摆脱530 MB的内部图像,因为它们不再需要。当我们不这样做时:机器会停止交换。

奖金阅读

对于任何喜欢这种答案风格的人(解释原因,以及如何变得明显),我建议你阅读Don Box的Essential COM第一章:

在35页中,他解释了使用二进制对象的问题,并在您眼前发明了COM。一旦你意识到COM的原因,剩下的300页是显而易见的,只是详细介绍了微软的实现。

我认为每个曾经处理过对象或COM的程序员至少应该阅读第一章。对任何事情都是最好的解释。

额外奖金阅读

Eric Lippert的When everything you know is wrong

因此,确实写一个正确的终结器是非常困难的,我能给你的最好的建议就是不要尝试。


6
投票

我不会重复关于使用或释放​​未受管理资源的常规内容,这些内容已全部涵盖。但我想指出似乎是一种常见的误解。 给出以下代码

Public Class LargeStuff
  Implements IDisposable
  Private _Large as string()

  'Some strange code that means _Large now contains several million long strings.

  Public Sub Dispose() Implements IDisposable.Dispose
    _Large=Nothing
  End Sub

我意识到Disposable实现不符合当前的指导原则,但希望你们都能理解。 现在,当调用Dispose时,释放多少内存? 答:没有。 调用Dispose可以释放非托管资源,它不能回收托管内存,只有GC可以做到这一点。多数民众赞成不是说上述不是一个好主意,遵循上述模式实际上仍然是一个好主意。一旦运行了Dispose,就没有什么能阻止GC重新声明_Large正在使用的内存,即使LargeStuff的实例可能仍然在范围内。 _Large中的字符串也可能在gen 0中,但LargeStuff的实例可能是gen 2,因此,内存将更快地被重新声明。 添加finaliser来调用上面显示的Dispose方法没有意义。这将延迟重新声明内存以允许终结者运行。


5
投票

除了主要用作控制系统资源生命周期的方法之外(完全由Ian的精彩答案所覆盖,我们也很感激!),IDisposable / using组合也可用于确定(关键)全局资源的状态变化范围:控制台,线程,进程,任何全局对象,如应用程序实例。

我写了一篇关于这种模式的文章:http://pragmateek.com/c-scope-your-global-state-changes-with-idisposable-and-the-using-statement/

它说明了如何以可重用和可读的方式保护一些常用的全局状态:控制台颜色,当前线程文化,Excel应用程序对象属性......


4
投票

如果有的话,我希望代码的效率低于离开它时的效率。

调用Clear()方法是不必要的,如果Dispose没有这样做,GC可能不会这样做......


2
投票

Dispose()操作在示例代码中有些事情可能会产生由于MyCollection对象的正常GC而不会发生的影响。

如果_theList_theDict引用的对象被其他对象引用,那么List<>Dictionary<>对象将不会被收集,但突然没有内容。如果示例中没有Dispose()操作,那些集合仍将包含其内容。

当然,如果是这种情况,我会称之为破碎的设计 - 我只是指出(迂腐地,我认为)Dispose()操作可能不是完全多余的,这取决于是否有其他用途的List<>Dictionary<>片段中未显示的内容。


2
投票

大多数“非托管资源”讨论的一个问题是它们并没有真正定义术语,但似乎暗示它与非托管代码有关。虽然许多类型的非托管资源确实与非托管代码进行交互,但是在这些术语中考虑非托管资源是没有用的。

相反,人们应该认识到所有管理资源的共同点:它们都需要一个对象,要求某些外部“事物”代表它做某事,损害其他一些“事物”,而另一个实体同意这样做直到另行通知。如果对象被抛弃并消失得无影无踪,那么就没有什么可以告诉外面的“事物”它不再需要代表不再存在的对象来改变它的行为;因此,“事物的有用性将永久地减少。

因此,一个非托管资源代表一些外部“事物”的协议,以代表一个对象改变它的行为,如果该对象被抛弃并不再存在,那将无用地损害该外部“事物”的有用性。托管资源是这样一个协议的受益人,但是如果它被放弃则已经注册接收通知,并且在被销毁之前将使用这样的通知将其事务整理好。


2
投票

IDisposable适合取消订阅活动。


2
投票

首先是定义。对我来说,非托管资源意味着一些类,它实现了IDisposable接口或使用dll调用创建的东西。 GC不知道如何处理这些对象。如果类仅具有值类型,那么我不认为此类是具有非托管资源的类。对于我的代码,我遵循以下做法:

  1. 如果我创建类使用一些非托管资源,那么这意味着我还应该实现IDisposable接口以清理内存。
  2. 我完成使用后立即清理对象。
  3. 在我的dispose方法中,我遍历所有类的IDisposable成员并调用Dispose。
  4. 在我的Dispose方法中调用GC.SuppressFinalize(this)以通知垃圾收集器我的对象已被清理。我这样做是因为调用GC是昂贵的操作。
  5. 作为额外的预防措施,我尝试多次调用Dispose()。
  6. 有时我添加私有成员_disposed和check in方法调用对象被清理。如果它被清理,那么生成ObjectDisposedException 以下模板演示了我在单词中描述的代码示例:

public class SomeClass : IDisposable
    {
        /// <summary>
        /// As usually I don't care was object disposed or not
        /// </summary>
        public void SomeMethod()
        {
            if (_disposed)
                throw new ObjectDisposedException("SomeClass instance been disposed");
        }

        public void Dispose()
        {
            Dispose(true);
        }

        private bool _disposed;

        protected virtual void Dispose(bool disposing)
        {
            if (_disposed)
                return;
            if (disposing)//we are in the first call
            {
            }
            _disposed = true;
        }
    }

2
投票

处理管理资源的最合理用例是为GC准备回收原本无法收集的资源。

一个主要的例子是循环引用。

虽然最佳做法是使用避免循环引用的模式,但如果你最终得到(例如)一个“子”对象,该对象的引用又回到了它的“父”,那么如果你放弃它就可以停止父级的GC收集引用并依赖GC - 如果你已经实现了终结器,它将永远不会被调用。

绕过此方法的唯一方法是通过在子项上将Parent引用设置为null来手动中断循环引用。

在父级和子级上实现IDisposable是执行此操作的最佳方式。在Parent上调用Dispose时,在所有子项上调用Dispose,在子Dispose方法中,将Parent references设置为null。


2
投票

您给出的代码示例不是IDisposable用法的好例子。字典清除通常不应该使用Dispose方法。当字典项超出范围时,它将被清除并处理。 IDisposable实现需要释放一些即使在超出范围之后也不会释放/释放的内存/处理程序。

以下示例显示了IDisposable模式的一个很好的示例,其中包含一些代码和注释。

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.
    }
}

-1
投票

我看到很多答案已经转移到谈论对托管和非托管资源使用IDisposable。我建议将这篇文章作为我发现如何实际使用IDisposable的最佳解释之一。

https://www.codeproject.com/Articles/29534/IDisposable-What-Your-Mother-Never-Told-You-About

对于实际问题;如果您使用IDisposable来清理占用大量内存的托管对象,那么简短的答案就是否定。原因是一旦你处理了IDisposable,你应该让它超出范围。此时,任何引用的子对象也超出范围并将被收集。

唯一真正的例外是,如果您在托管对象中占用了大量内存,并且您已阻止该线程等待某些操作完成。如果在完成调用之后不需要那些对象,那么将这些引用设置为null可能允许垃圾收集器更快地收集它们。但是这种情况会代表需要重构的坏代码 - 而不是IDisposable的用例。


58
投票

IDisposable通常用于利用using语句,并利用一种简单的方法对托管对象进行确定性清理。

public class LoggingContext : IDisposable {
    public Finicky(string name) {
        Log.Write("Entering Log Context {0}", name);
        Log.Indent();
    }
    public void Dispose() {
        Log.Outdent();
    }

    public static void Main() {
        Log.Write("Some initial stuff.");
        try {
            using(new LoggingContext()) {
                Log.Write("Some stuff inside the context.");
                throw new Exception();
            }
        } catch {
            Log.Write("Man, that was a heavy exception caught from inside a child logging context!");
        } finally {
            Log.Write("Some final stuff.");
        }
    }
}

37
投票

Dispose模式的目的是提供一种清理托管和非托管资源的机制,何时发生这种情况取决于如何调用Dispose方法。在您的示例中,Dispose的使用实际上并没有执行与dispose相关的任何操作,因为清除列表对正在处理的集合没有影响。同样,将变量设置为null的调用也不会对GC产生影响。

您可以查看此article以获取有关如何实现Dispose模式的更多详细信息,但它基本上如下所示:

public class SimpleCleanup : IDisposable
{
    // some fields that require cleanup
    private SafeHandle handle;
    private bool disposed = false; // to detect redundant calls

    public SimpleCleanup()
    {
        this.handle = /*...*/;
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                // Dispose managed resources.
                if (handle != null)
                {
                    handle.Dispose();
                }
            }

            // Dispose unmanaged managed resources.

            disposed = true;
        }
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
}

这里最重要的方法是Dispose(bool),它实际上在两种不同的情况下运行:

  • disposing == true:该方法由用户代码直接或间接调用。可以处理托管和非托管资源。
  • disposing == false:运行时从终结器内部调用该方法,不应引用其他对象。只能处理非托管资源。

简单地让GC负责清理的问题在于你无法真正控制GC何时运行一个收集周期(你可以调用GC.Collect(),但你真的不应该这样做)所以资源可能会停留比需要的时间更长。请记住,调用Dispose()实际上不会导致收集周期或以任何方式导致GC收集/释放对象;它只是提供了更加确定性地清理所用资源的方法,并告诉GC已经执行了这次清理。

IDisposable和处理模式的重点不在于立即释放内存。调用Dispose实际上甚至有可能立即释放内存的唯一一次是它处理disposing == false场景并操纵非托管资源。对于托管代码,内存实际上不会被回收,直到GC运行一个收集周期,你实际上无法控制(除了调用GC.Collect(),我已经提到过这不是一个好主意)。

由于.NET中的字符串不使用任何非托管资源并且未实现IDisposable,因此您的方案无法真正有效,因此无法强制它们“清理”。


17
投票

在调用Dispose之后,不应再调用对象的方法(尽管对象应该容忍对Dispose的进一步调用)。因此问题中的例子很愚蠢。如果调用Dispose,则可以丢弃对象本身。因此,用户应该放弃对该整个对象的所有引用(将它们设置为null),并且内部的所有相关对象将自动清除。

至于关于托管/非托管的一般问题以及其他答案中的讨论,我认为对这个问题的任何答案都必须从非托管资源的定义开始。

它归结为有一个函数你可以调用将系统置于一个状态,还有一个函数你可以调用它将它带回该状态。现在,在典型示例中,第一个可能是返回文件句柄的函数,第二个可能是对CloseHandle的调用。

但是 - 这是关键 - 它们可以是任何匹配的功能对。一个建立一个国家,另一个撕毁它。如果状态已经构建但尚未拆除,则存在资源的实例。您必须安排在正确的时间进行拆解 - 资源不由CLR管理。唯一自动管理的资源类型是内存。有两种:GC和堆栈。值类型由堆栈管理(或通过在引用类型内搭接),引用类型由GC管理。

这些函数可能导致状态更改,可以自由交错,或者可能需要完美嵌套。状态更改可能是线程安全的,也可能不是。

看看Justice的问题中的例子。对日志文件缩进的更改必须完全嵌套,否则一切都会出错。他们也不太可能是线程安全的。

可以与垃圾收集器搭便车,以清理您的非托管资源。但是,只有状态更改函数是线程安全的,并且两个状态的生命周期才能以任何方式重叠。因此,Justice的资源示例必须没有终结器!这对任何人都没有帮助。

对于这些类型的资源,您可以实现IDisposable,而无需终结器。终结者绝对是可选的 - 必须是。这在许多书中被掩盖或甚至没有提及。

然后你必须使用using语句才有机会确保调用Dispose。这基本上就像搭便车一样(所以终结器是GC,using是堆栈)。

缺少的部分是你必须手动编写Dispose并调用你的字段和基类。 C ++ / CLI程序员不必这样做。在大多数情况下,编译器会为它们编写它。

有一个替代方案,我更喜欢那些完全嵌套并且不是线程安全的状态(除了其他任何东西,避免IDisposable,你可以避免与一个无法抗拒为每个实现IDisposable的类添加终结器的人争论的问题) 。

而不是写一个类,你写一个函数。该函数接受委托回调:

public static void Indented(this Log log, Action action)
{
    log.Indent();
    try
    {
        action();
    }
    finally
    {
        log.Outdent();
    }
}

然后一个简单的例子是:

Log.Write("Message at the top");
Log.Indented(() =>
{
    Log.Write("And this is indented");

    Log.Indented(() =>
    {
        Log.Write("This is even more indented");
    });
});
Log.Write("Back at the outermost level again");

传入的lambda作为一个代码块,所以就像你制作自己的控制结构来实现与using相同的目的,除了你不再有任何打电话滥用它的危险。他们无法无法清理资源。

如果资源是可能具有重叠生命周期的那种技术,那么这种技术就不那么有用了,因为那时你希望能够构建资源A,然后是资源B,然后杀死资源A然后杀死资源B.你不能这样做如果你强迫用户像这样完美地嵌套。但是你需要使用IDisposable(但仍然没有终结器,除非你已经实现了线程安全,这不是免费的)。


14
投票

方案我使用IDisposable:清理非托管资源,取消订阅事件,关闭连接

我用来实现IDisposable(不是线程安全)的成语:

class MyClass : IDisposable {
    // ...

    #region IDisposable Members and Helpers
    private bool disposed = false;

    public void Dispose() {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    private void Dispose(bool disposing) {
        if (!this.disposed) {
            if (disposing) {
                // cleanup code goes here
            }
            disposed = true;
        }
    }

    ~MyClass() {
        Dispose(false);
    }
    #endregion
}

11
投票

是的,该代码是完全冗余和不必要的,并且它不会使垃圾收集器做任何事情本来不会做的事情(一旦MyCollection的实例超出范围,就是这样。)尤其是.Clear()调用。

回答你的编辑:排序。如果我这样做:

public void WasteMemory()
{
    var instance = new MyCollection(); // this one has no Dispose() method
    instance.FillItWithAMillionStrings();
}

// 1 million strings are in memory, but marked for reclamation by the GC

出于内存管理的目的,它在功能上与此相同:

public void WasteMemory()
{
    var instance = new MyCollection(); // this one has your Dispose()
    instance.FillItWithAMillionStrings();
    instance.Dispose();
}

// 1 million strings are in memory, but marked for reclamation by the GC

如果你真的真的需要在瞬间释放内存,请致电GC.Collect()。但是,这里没有理由这样做。在需要时将释放内存。


11
投票

如果MyCollection无论如何都要进行垃圾收集,那么你就不需要处理它了。这样做只会使CPU超过必要的流失,甚至可能使垃圾收集器已经执行的一些预先计算的分析无效。

我使用IDisposable来做一些事情,比如确保正确处理线程,以及非托管资源。

编辑回应斯科特的评论:

GC性能指标受影响的唯一时间是调用[sic] GC.Collect()的时间。

从概念上讲,GC维护对象引用图的视图,以及从线程的堆栈帧对它的所有引用。这个堆可能非常大并且跨越许多页面的内存。作为优化,GC会缓存对不太可能经常更改的页面的分析,以避免不必要地重新扫描页面。当页面中的数据发生更改时,GC会从内核接收通知,因此它知道页面很脏并需要重新扫描。如果集合在Gen0中,那么页面中的其他内容可能也会发生变化,但这在Gen1和Gen2中的可能性较小。有趣的是,这些挂钩在Mac OS X中不适用于将GC移植到Mac以便在该平台上运行Silverlight插件的团队。

反对不必要的资源处置的另一点:想象一个过程正在卸载的情况。想象一下,这个过程已经运行了一段时间。有可能该进程的许多内存页面已被交换到磁盘。至少他们不再处于L1或L2缓存中。在这种情况下,卸载的应用程序没有必要将所有这些数据和代码页交换回内存,以“释放”当进程终止时将由操作系统释放的资源。这适用于托管甚至某些非托管资源。只有处理非后台线程活动的资源必须处理,否则进程将保持活动状态。

现在,在正常执行期间,必须正确清理短暂的资源(因为@fezmonkey指出数据库连接,套接字,窗口句柄)以避免非托管内存泄漏。这些是必须处理的事物。如果你创建一个拥有一个线程的类(并且拥有我的意思是它创建了它,因此负责确保它停止,至少通过我的编码风格),那么该类很可能必须实现IDisposable并拆除线程Dispose

.NET框架使用IDisposable接口作为信号,甚至警告开发人员必须处理此类。我想不出框架中实现IDisposable(不包括显式接口实现)的任何类型,其中dispos是可选的。


7
投票

如果要立即删除,请使用非托管内存。

看到:


6
投票

在您发布的示例中,它仍然不“立即释放内存”。所有内存都是垃圾回收,但它可能允许在早期的generation中收集内存。你必须运行一些测试才能确定。


框架设计指南是指南,而不是规则。它们告诉您接口主要用于什么,何时使用它,如何使用它以及何时不使用它。

我曾经使用IDisposable读取了一个简单的RollBack()故障代码。下面的MiniTx类将检查Dispose()上的标志,如果Commit调用从未发生过,那么它将自己调用Rollback。它添加了一层间接,使调用代码更容易理解和维护。结果看起来像:

using( MiniTx tx = new MiniTx() )
{
    // code that might not work.

    tx.Commit();
} 

我也看到计时/日志代码做同样的事情。在这种情况下,Dispose()方法停止计时器并记录该块已退出。

using( LogTimer log = new LogTimer("MyCategory", "Some message") )
{
    // code to time...
}

所以这里有几个具体的例子,它们不做任何非托管资源清理,但是成功地使用了IDisposable来创建更干净的代码。

© www.soinside.com 2019 - 2024. All rights reserved.