C# 清理IDisposable资源

C# 清理IDisposable资源,c#,garbage-collection,idisposable,C#,Garbage Collection,Idisposable,现在我明白了,当我使用完实现IDisposable的资源后,我应该调用Dispose方法来清理资源 我应该在多大程度上这样做 我的例子是: 我正在代码中创建一个NotifyIcon,因此我执行以下操作: var icon = new Icon(...); var contextMenu = new ContextMenu(...); var contextMenuButton1 = new MenuItem(...); var contextMenuButton2 = new MenuItem(

现在我明白了,当我使用完实现IDisposable的资源后,我应该调用Dispose方法来清理资源

我应该在多大程度上这样做

我的例子是:

我正在代码中创建一个NotifyIcon,因此我执行以下操作:

var icon = new Icon(...);
var contextMenu = new ContextMenu(...);
var contextMenuButton1 = new MenuItem(...);
var contextMenuButton2 = new MenuItem(...);
var contextMenuButton3 = new MenuItem(...);
// ...
var notifyIcon = new NotifyIcon(...);
所有这些都有一个Dispose方法。通常我只会保留对notifyIcon的引用,并将其处理掉

但是,它不会处理图标、上下文菜单或其项,所以我应该保留对所有内容的引用,或者至少有一个列表吗


或者我可以假设,由于这些的作用域,当我处理NotifyIcon时,垃圾收集器会为我调用dispose吗?

通常,这就是using语句的用途,例如:

using (var icon = new Icon(...))
{
}
using语句在结束后立即调用Dispose方法,即使内部存在异常,它仍然会正确地处理变量。您甚至可以链接它们以减少嵌套:

using (var icon = new Icon(""))
using (var icon2 = new Icon(""))
using (var icon3 = new Icon(""))
{

}
你想尽快打电话给Dispose。如果您不这样做,它仍将在某个时间被调用,通常是在对象的最终确定期间,但这可能已经太晚了。例如,处理文件句柄将允许打开另一个文件句柄,而如果您没有足够快地处理它,第二次调用将抛出异常

之所以有Dispose方法,首先是因为Icon在后台有一些本机资源,它使用本机Icon对象,这都与Windows和GDI+有关。这些资源是稀缺的,一个更好的例子是文件句柄,因此您不想等待GC最终收集它们-这更昂贵,如果大量完成,最终确定会带来不小的成本,甚至可以通过图标阻止对资源的访问,我记得有一个问题,在这个问题上,每个进程只能分配这么多本机位图,然后你就会挨饿

如果您不关心成本,如果资源不够稀缺,而且如果使用或处置比它的价值更麻烦,那么您不必调用Dispose。它最终会被调用


例如,您应该始终手动调用dispose:文件、流、套接字、大块本机内存。

垃圾收集器最终会为所有对象调用dispose,假设它们是根据IDisposable指南实现的,但作为最佳实践,您应该始终自己执行。这只是一个最佳实践,它还可以让您检测潜在的问题区域,例如,一个大图像在内存中保存的时间太长-因为您将手动处理它,如果您的应用程序试图在您不打算使用它的地方使用它,它将抛出异常

using (var icon = new Icon(...))
using (var contextMenu = new ContextMenu(...))
using (var contextMenuButton1 = new MenuItem(...))
using (var contextMenuButton2 = new MenuItem(...))
using (var contextMenuButton3 = new MenuItem(...))
{
    // ...
}

this.notifyIcon = new NotifyIcon(...);
由于您正在存储this.notifyIcon值,因此我假定在此代码示例中,您的类还必须实现IDisposable并在那里调用this.notifyIcon.Dispose


编辑:对于那些表示GC不会调用Dispose的注释-实现IDisposable的标准模式是实现调用this.Dispose的析构函数-由于GC确实调用析构函数/终结器,它也将间接调用Dispose

这在很大程度上取决于场景。例如,在许多UI框架中,向父控件/容器添加菜单项或图标之类的内容会使父控件/容器承担该项的责任。因此,处置父级将同时处置所有祖先。所以:如果我们假设您正在创建的这些新控件将被添加到UI中,那么不:您通常不需要手动执行任何操作。但是,如果您经常添加/删除控件,则需要确定您何时获得某个元素的所有权,也就是说,如果您从UI中删除该元素,则该元素现在是您的,而不是UI的-处理该元素是您的责任。

来自:`

创建新的NotifyIcon时。。。[它]将一直存在,直到其容器将其释放到垃圾收集

如果使用的构造函数接受的容器通常是表单拥有的System.ComponentModel.container,则在容器被释放时(即表单关闭时)将调用Dispose

上面链接的MSDN文档中的示例以这种方式使用表单的容器


如果使用默认构造函数,则没有容器,您必须自己管理NotifyIcon的生存期,并在完成后进行处置。

您只需处置NotifyIcon,理想情况下,NotifyIcon应在其实现中处置其子项。然而,如果有疑问,您可以使用源代码工具(如折射器或DotPeek)来查看它是否正确。否则,请准备一个一次性项目列表,并在最后对每个项目调用dispose。有关此操作的详细信息:GC从不调用dispose,它只会对声明一个且不再可访问时未抑制终结的对象调用终结器。P
租约修复:GC从不调用Dispose。如果类实现正确,它会间接调用Dispose。因为实现的标准模式是添加析构函数~ClassName{this.Disposefalse;}@Knaģis:当Microsoft建议其Dispose模式时,人们认为类将封装托管和非托管资源的混合体。很明显,更好的模式是将单个非托管资源封装到私有对象中,这些私有对象被编码为托管资源。因为包装器对象是私有的,所以它们不必是公共的,更不用说是可公开继承的,因此不必遵循Microsoft最初的Dispose模式。@Knaģis:此外,如果一个短期对象订阅了来自长期对象的事件,然后,我们必须接受这样一个事实:如果在长寿命对象的生命周期内可以创建无限数量的短命对象,那么处置短命对象的失败会将其生命周期延长到长寿命对象的生命周期,从而造成内存泄漏,或者施加大量额外的复杂性和开销来防止这种情况发生微软文档中的主要缺陷在于它没有始终如一地记录所有权。我无法辨别任何特定的模式或逻辑,哪些对象属性意味着所有权,哪些不意味着所有权。控件的字体属性在这方面特别奇怪;我没有看到任何正式的文档记录它的行为,所以我不知道它有什么保证或没有保证。非托管资源不必是本地资源,也不必是稀缺资源。当一个对象要求外部实体代表它做一些损害其他实体利益的事情,直到另行通知时,它会获取一个非托管资源。如果IEnumerator的实现在构造时获取锁,并在Dispose时释放它,则该锁将是非托管资源,即使它完全在托管框架内处理。此外,不仅许多这样的IEnumerator实现不会实现终结器,而且即使……它们实现了,它们也无能为力;除非IEnumerator是在终结器线程上构造的,否则终结器将无法释放其锁。可以肯定的是,枚举数通常不应该在整个枚举过程中获取并持有锁,并且必须小心使用持有幸运的枚举数以避免死锁,但是让枚举持有锁有时比任何实际的替代方法都不那么有害。