通过阅读Microsoft文档,我知道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包含一百万个字符串,并且您希望现在释放内存,而不是等待垃圾收集器。上面的代码能做到这一点吗?


当前回答

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

我使用IDisposable来执行诸如确保线程以及非托管资源被正确处理之类的操作。

编辑回应斯科特的评论:

GC性能指标唯一受到影响的时间是调用〔sic〕GC.Collect()时“

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

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

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

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

其他回答

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

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

当然,如果是这种情况,我会称之为一个坏的设计——我只是指出(我想是迂腐的)Dispose()操作可能不是完全冗余的,这取决于List<>或Dictionary<>是否有其他未在片段中显示的用途。

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

See:

马歇尔.AllocHGlobalMarshal.FreeHGlobal公司编组.仓库结构

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


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

我曾经读过一段代码,它是一个利用IDisposable失败时的简单RollBack()。下面的MiniTx类将检查Dispose()上的一个标志,如果Commit调用从未发生,它将调用回滚。它添加了一层间接层,使调用代码更易于理解和维护。结果如下:

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创建了更干净的代码。

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

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

公共类SomeClass:IDisposable{///<summary>///像往常一样,我不在乎对象是否被处理///</summary>public void SomeMethod(){如果(_disposed)引发新的ObjectDisposedException(“已释放SomeClass实例”);}public void Dispose(){Dispose(真);}私有bool _disposed;受保护的虚拟void Dispose(bool dispositing){如果(_disposed)回来如果(处置)//我们在第一次通话中{}_已处理=真;}}

处置托管资源最合理的用例是为GC回收资源做准备,否则这些资源将永远不会被收集。

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

虽然使用避免循环引用的模式是最佳做法,但如果您最终(例如)得到了一个“子”对象,该对象的引用返回到其“父”,那么如果您只是放弃引用并依赖GC,则这可能会停止父对象的GC收集,而且如果您实现了终结器,则永远不会调用它。

解决此问题的唯一方法是通过将子级上的Parent引用设置为null来手动打破循环引用。

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