关于C#:正确使用IDisposable接口

Proper use of the IDisposable interface

我从微软文档中了解到,IDisposable接口的"主要"用途是清除非托管资源。

对我来说,"非托管"指的是数据库连接、套接字、窗口句柄等。但是,我看到过一些代码,其中实现Dispose()方法是为了释放托管资源,这对我来说似乎是多余的,因为垃圾收集器应该为您负责。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
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包含一百万个字符串,您希望现在释放内存,而不是等待垃圾收集器。上面的代码能完成吗?


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

Note: What is an unmanaged resource? If you found it in the Microsoft .NET Framework: it's managed. If you went poking around MSDN yourself, it's unmanaged. Anything you've used P/Invoke calls to get outside of the nice comfy world of everything available to you in the .NET Framework is unmanaged – and you're now responsible for cleaning it up.

Ok.

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

1
public void Cleanup()

或好的。

1
public void Shutdown()

但是,这种方法有一个标准化的名称:好的。

1
public void Dispose()

甚至还创建了一个接口,IDisposable,它只有一个方法:好的。

1
2
3
4
public interface IDisposable
{
   void Dispose()
}

因此,您让对象公开IDisposable接口,这样您就保证编写了一个方法来清理非托管资源:好的。

1
2
3
4
public void Dispose()
{
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
}

你就完了。除非你能做得更好。好的。

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

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

所以现在我们将:好的。

  • 摆脱非托管资源(因为我们必须这样做),以及
  • 摆脱被管理的资源(因为我们希望有帮助)

因此,让我们更新我们的Dispose()方法来消除这些托管对象:好的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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()怎么办?然后他们会泄露一些未管理的资源!好的。

Note: They won't leak managed resources, because eventually the garbage collector is going to run, on a background thread, and free the memory associated with any unused objects. This will include your object, and any managed objects you use (e.g. the Bitmap and the DbConnection).

Ok.

如果这个人忘记打电话给Dispose(),我们仍然可以挽救他们的培根!我们仍然有一种方法可以为它们调用它:当垃圾收集器最终开始释放(即完成)我们的对象时。好的。

Note: The garbage collector will eventually free all managed objects.
When it does, it calls the Finalize
method on the object. The GC doesn't know, or
care, about your Dispose method.
That was just a name we chose for
a method we call when we want to get
rid of unmanaged stuff.

Ok.

垃圾收集器销毁我们的对象是释放这些烦人的非托管资源的最佳时机。我们通过重写Finalize()方法来实现这一点。好的。

Note: In C#, you don't explicitly override the Finalize() method.
You write a method that looks like a C++ destructor, and the
compiler takes that to be your implementation of the Finalize() method:

Ok.

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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()相反),则传递一个布尔值,这意味着释放托管资源是安全的。好的。

这个内部方法可以被赋予一些任意的名称,如"corepose"或"myInternalDispose",但传统上称它为Dispose(Boolean):好的。

1
protected void Dispose(Boolean disposing)

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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()方法的实现更改为:好的。

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

最后定稿人:好的。

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

Note: If your object descends from an object that implements Dispose, then don't forget to call their base Dispose method when you override Dispose:

Ok.

1
2
3
4
5
6
7
8
9
10
11
public override void Dispose()
{
    try
    {
        Dispose(true); //true: safe to free managed resources
    }
    finally
    {
        base.Dispose();
    }
}

一切都很好,除了你能做得更好!好的。

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

这不仅是浪费,而且如果您的对象对上次调用Dispose()时已处理的对象有垃圾引用,您将尝试再次处理它们!好的。

您会注意到在我的代码中,我小心地删除了对我已处理的对象的引用,因此我不会在垃圾对象引用上调用Dispose。但这并没有阻止一只小虫子爬进来。好的。

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

1
2
3
4
5
6
protected void Dispose(Boolean iAmBeingCalledFromDisposeAndNotFinalize)
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle); //<--double destroy
   ...
}

解决这个问题的方法是告诉垃圾收集器,它不需要费心完成对象——它的资源已经被清理,不需要再做任何工作。通过在Dispose()方法中调用GC.SuppressFinalize()来实现:好的。

1
2
3
4
5
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的文件中说:好的。

The Finalize method is used to perform cleanup operations on unmanaged resources held by the current object before the object is destroyed.

Ok.

但是,对于IDisposable.Dispose来说,msdn文档也指出:好的。

Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.

Ok.

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

It's your choice! But choose Dispose.

Ok.

您当然可以将非托管清理放到终结器中:好的。

1
2
3
4
5
6
7
~MyObject()
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);

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

问题在于,您不知道垃圾收集器何时可以完成对象。您的未管理、不需要、未使用的本地资源将一直保留,直到垃圾收集器最终运行。然后它将调用终结器方法;清理非托管资源。Object.Finalize的文档指出:好的。

The exact time when the finalizer executes is undefined. To ensure deterministic release of resources for instances of your class, implement a Close method or provide a IDisposable.Dispose implementation.

Ok.

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

回答你最初的问题:为什么不现在释放内存,而不是当GC决定释放内存时?我有一个面部识别软件,现在需要去掉530MB的内部图像,因为它们不再需要了。当我们不这样做时:机器会磨成一个交换停止。好的。奖金读数

对于任何喜欢这个答案风格的人(解释原因,以及如何变得明显),我建议你阅读Don Box's Essential Com:好的。

  • 直接链接:皮尔逊出版社第一章样本
  • 磁铁:84BF0B960936D677190A2BE355858E80EF7542C0

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

我认为每一个处理过对象或COM的程序员至少应该读第一章。这是有史以来最好的解释。好的。额外奖励阅读

当你所知道的一切都被埃里克·利珀特错了好的。

It is therefore very difficult indeed to write a correct finalizer,
and the best advice I can give you is to not try.

Ok.

好啊。


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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.");
        }
    }
}


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

您可以查看本文,了解有关如何实现Dispose模式的更多详细信息,但它基本上是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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模式的关键不是立即释放内存。调用dispose甚至有机会立即释放内存的唯一时间是处理disposing==false方案和操作非托管资源。对于托管代码,在GC运行一个收集周期之前,实际上不会回收内存,而这个收集周期实际上是您无法控制的(除了调用gc.collect(),我已经提到过这不是一个好主意)。

由于.NET中的字符串不使用任何未管理的资源,也不实现IDisposable,因此您的方案实际上无效,无法强制它们"清理"。


调用Dispose之后,不应再调用对象的方法(尽管对象应允许进一步调用Dispose)。因此,这个问题的例子是愚蠢的。如果调用Dispose,则可以丢弃对象本身。因此,用户应该放弃对整个对象的所有引用(将其设置为空),它内部的所有相关对象将自动被清除。

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

归根结底,你可以调用一个函数来将系统置于一个状态,还有另一个函数可以让它从那个状态恢复过来。现在,在典型的例子中,第一个函数可能是返回文件句柄的函数,第二个函数可能是对CloseHandle的调用。

但是-这是关键-它们可以是任何匹配的函数对。一个建立了一个国家,另一个摧毁了它。如果状态已生成但尚未被删除,则存在该资源的一个实例。您必须安排在正确的时间进行拆卸-资源不是由clr管理的。唯一自动管理的资源类型是内存。有两种:GC和堆栈。值类型由堆栈管理(或通过在引用类型中挂接一个工具),引用类型由GC管理。

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

看看正义问题中的例子。对日志文件缩进的更改必须完全嵌套,否则都会出错。而且它们不太可能是螺纹安全的。

可以搭上垃圾收集器来清理未管理的资源。但是,只有当状态更改函数是线程安全的,并且两个状态的生命周期可以以任何方式重叠。所以正义的资源例子不能有终结器!这对任何人都没有帮助。

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

然后,您必须使用using语句来确保调用Dispose。这基本上就像是在堆栈上搭便车(因此,由于终结器是在GC上,using是在堆栈上)。

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

还有一种选择,我更喜欢嵌套完美且不是线程安全的状态(除了其他任何情况外,避免IDisposable使您免去了与无法拒绝向实现IDisposable的每个类添加终结器的人发生争论的问题)。

不是编写类,而是编写函数。函数接受一个委托,以回调到:

1
2
3
4
5
6
7
8
9
10
11
12
public static void Indented(this Log log, Action action)
{
    log.Indent();
    try
    {
        action();
    }
    finally
    {
        log.Outdent();
    }
}

然后一个简单的例子是:

1
2
3
4
5
6
7
8
9
10
11
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(但是仍然没有终结器,除非您已经实现了线程安全,这不是免费的)。


我使用IDisposable的场景:清理非托管资源、取消订阅事件、关闭连接

我用于实现IDisposable(而不是threadsafe)的习惯用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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
}


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

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

根据斯科特的评论进行编辑:

The only time the GC performance metrics are affected is when a call the [sic] GC.Collect() is made"

从概念上讲,GC维护对象引用图的视图,以及从线程的堆栈帧对它的所有引用。这个堆可能非常大,跨越许多内存页。作为一种优化,GC缓存其对不太可能经常更改的页面的分析,以避免不必要地重新扫描页面。当页面中的数据发生变化时,GC从内核接收通知,因此它知道页面变脏了,需要重新扫描。如果集合是gen0的,那么页面中的其他内容也可能发生变化,但gen1和gen2中的变化不大。有趣的是,对于将gc移植到mac以使Silverlight插件在该平台上工作的团队来说,mac os x中没有这些钩子。

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

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

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


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

回答你的编辑:有点。如果我这样做:

1
2
3
4
5
6
7
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

在内存管理方面,它的功能与此相同:

1
2
3
4
5
6
7
8
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()。不过,这里没有理由这么做。内存将在需要时释放。


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

见:

  • 全局封送处理
  • 弗里赫格罗巴元帅
  • 驱逐舰结构

我不会重复那些关于使用或释放未管理资源的常规内容,这些都已经被涵盖了。但我想指出一个常见的误解。给出以下代码

1
2
3
4
5
6
7
8
9
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

我认识到一次性实现并不遵循当前的指导方针,但希望你们都能理解这个想法。现在,当调用Dispose时,释放了多少内存?答:没有。调用Dispose可以释放非托管资源,它不能回收托管内存,只有GC可以这样做。这并不是说上面的模式不是一个好主意,遵循上面的模式实际上仍然是一个好主意。一旦Dispose被运行,就不会停止GC重新声明大型使用的内存,即使largestuff的实例可能仍然在作用域中。大字符串也可能在0代中,但largestuff的实例可能是2代,因此,内存将很快被重新声明。但是,没有必要添加一个决赛选手来调用上面显示的Dispose方法。这只会延迟对记忆力的重新要求,让决赛选手能够参加比赛。


在您发布的示例中,它仍然没有"立即释放内存"。所有内存都是垃圾收集的,但它可能允许在早期版本中收集内存。你得做些测试才能确定。

框架设计指南是指导方针,而不是规则。它们告诉您接口的主要用途、何时使用、如何使用以及何时不使用。

我曾经读过一段代码,它是在使用IDisposable失败时的一个简单的rollback()。下面的minitx类将检查dispose()上的标志,如果没有发生Commit调用,那么它将自己调用Rollback。它增加了一个间接层,使得调用代码更容易理解和维护。结果是:

1
2
3
4
5
6
using( MiniTx tx = new MiniTx() )
{
    // code that might not work.

    tx.Commit();
}

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

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

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


除了它作为一种控制系统资源生命周期的方法的主要用途之外(完全被Ian的令人敬畏的回答所覆盖,Kudos!),IDisposable/Using组合也可用于范围(关键)全局资源的状态更改:控制台、线程、进程、任何全局对象(如应用程序实例)。

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

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


如果有什么不同的话,我希望代码的效率比不使用代码时要低。

调用clear()方法是不必要的,如果Dispose不这样做,GC可能不会这样做…


在示例代码中,Dispose()操作所做的一些操作可能会产生由于MyCollection对象的正常GC而不会发生的效果。

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

当然,如果这是一种情况,我会称之为破坏性设计——我只是指出(在学术上,我想)根据片段中没有显示的List<>Dictionary<>的其他用途,Dispose()操作可能不是完全冗余的。


IDisposable有助于取消订阅活动。


大多数讨论"非托管资源"的一个问题是,它们没有真正定义这个术语,但似乎意味着它与非托管代码有关。虽然许多类型的非托管资源确实与非托管代码进行了接口,但从这种角度考虑非托管资源并没有帮助。

相反,人们应该认识到所有管理资源的共同点:它们都需要一个对象要求一些外部的"事情"代表它做一些事情,损害一些其他的"事情",而另一个实体同意这样做,直到进一步通知。如果这个物体被遗弃,消失得无影无踪,那么外界的"物体"就再也不需要为了不再存在的物体而改变它的行为了;因此,"物体"的有用性将被永久性地削弱。

因此,非托管资源表示某个外部"物"代表某个对象改变其行为的协议,如果该对象被放弃并不再存在,这将毫无用处地损害该外部"物"的有用性。管理资源是作为此类协议受益人的对象,但如果被放弃,则已签署接收通知的对象,并且将使用此类通知在其被销毁之前将其事务整理好。


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

  • 如果由我创建的类使用一些非托管资源,那么它意味着我还应该实现IDisposable接口以清理内存。
  • 我一用完东西就把它清理干净。
  • 在Dispose方法中,我迭代类的所有IDisposable成员并调用Dispose。
  • 在Dispose方法中,调用gc.SuppressFinalize(this),以便通知垃圾回收器我的对象已被清理。我这样做是因为呼叫GC是昂贵的操作。
  • 作为额外的预防措施,我尝试多次调用Dispose()。
  • 有时我会添加私有成员释放并签入方法调用是否清除了对象。如果已清除,则生成ObjectDisposedException下面的模板演示了我用文字描述的代码示例:
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    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;
            }
        }


    处置管理资源最合理的用例是为GC准备回收那些原本不可能收集到的资源。

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

    虽然使用避免循环引用的模式是最好的做法,但是如果您最终得到(例如)一个引用回其"parent"的"child"对象,如果您只是放弃引用并依赖gc-plus(如果已实现终结器),则可能会停止父对象的gc集合,而不会调用它。

    唯一的方法是通过在子级上将父级引用设置为空来手动中断循环引用。

    在父级和子级上实现IDisposable是实现这一点的最佳方法。当对父级调用Dispose时,对所有子级调用Dispose,在子Dispose方法中,将父级引用设置为空。


    对于IDisposable的用法,给定的代码示例不是一个很好的例子。字典清理通常不应使用Dispose方法。字典项超出范围时将被清除和释放。IDisposable实现需要释放一些即使超出范围也不会释放/释放的内存/处理程序。

    下面的示例显示了一个带有一些代码和注释的IDisposable模式的好示例。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    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。我建议将本文作为我发现的关于如何实际使用IDisposable的最佳解释之一。

    https://www.codeproject.com/articles/29534/idisposable-what-your-mother-never-teld-you-about

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

    唯一真正的例外是,如果在托管对象中捆绑了大量的内存,并且您已经阻塞了等待某个操作完成的线程。如果在调用完成后不需要这些对象,那么将这些引用设置为空可能会使垃圾收集器更快地收集它们。但是,该场景表示需要重构的坏代码,而不是IDisposable的用例。