IDisposable实现 – ‘if(disposing)’应该包含什么

我一直在winforms应用程序中修复一些内存泄漏问题,并注意到一些非显式处置的一次性对象(开发人员没有调用Dispose方法)。 Finalize方法的实现也没有用,因为它没有进入if (disposing)子句。 所有静态事件取消注册和收集清除都已放入if (disposing)子句中。 如果对象是一次性的,最好的做法就是调用Dispose,但不幸的是,有时会发生这种情况

如果存在非托管对象,静态事件处理程序和一些需要在处置时清除的托管集合。 什么是决定什么应该进入的方式以及if (disposing)条款应该采用什么方式。

处理方法。

 // 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) { if (!disposed) { if (disposing) { // Free other state (managed objects). } // Free your own state (unmanaged objects). // Set large fields to null. disposed = true; } } 

它说管理对象应该在if (disposing) ,只有在开发人员显式调用Dispose方法时才能正常执行。 如果已经实现了Finalize方法并且开发人员忘记调用Dispose方法,那么通过Finalizer执行的执行不会进入if (disposing)部分。

以下是我的问题。

  1. 如果我有静态事件处理程序导致内存泄漏我应该在哪里取消注册? 进出if (disposing)条款?

  2. 如果我有一些导致内存泄漏的集合,我应该在哪里清除它们? 进出if (disposing)条款?

  3. 如果我使用第三方一次性对象(例如:devExpress winform控件),我不确定它们是托管对象还是非托管对象。 假设我想在处理表单时处理它们。 我怎样才能知道什么是托管对象以及什么是非托管对象? 一次性不说吗? 在这种情况下,如何决定应该采用什么以及if (disposing)条款应该采用什么?

  4. 如果我不确定管理或解除管理的事情,那么从if (disposing)条款中处理/清除/取消注册事件可能会产生什么不良后果? 让我们说它在处置之前检查null?

编辑

我的意思是事件取消注册就像下面这样。 Publisher是一个长期存在的实例,下面的行位于订阅者的构造函数中。 在这种情况下,订户需要取消注册事件并在发布者之前进行处置。

 publisher.DoSomeEvent += subscriber.DoSomething; 

这里要记住的关键是IDisposable的目的。 它的工作是帮助您在对象被垃圾回收之前 确定性地释放代码所持有的资源。 这实际上是C#语言团队选择使用关键字的原因,因为括号确定了应用程序所需的对象及其资源的范围。

例如,如果打开与数据库的连接,则需要释放该连接并在完成连接后尽快关闭它,而不是等待下一次垃圾回收。 这就是您实施Disposer的地方和原因。

第二种情况是协助处理非托管代码。 实际上这与操作系统的C ++ / C API调用有关,在这种情况下,您有责任确保代码不会泄露。 尽管.Net被编写为简单的P / Invoke到现有的Win32 API,但这种情况非常普遍。 从操作系统(例如Mutex)封装资源的任何对象都将实现Disposer,以允许您安全且确定地释放它的资源。

但是,这些API还将实现析构函数,以保证如果您不正确使用资源,它将不会被操作系统泄露。 当你的终结器被调用时,你不知道你引用的其他对象是否已被垃圾收集,这就是为什么对它们进行函数调用是不安全的(因为它们可能抛出NullReferenceException ),只有非托管引用(根据定义,不能进行垃圾收集)将可用于终结器。

希望有点帮助。

从广义上讲,托管资源在内部if (disposing)和非托管资源之外if (disposing) 。 处置模式如下:

  1. if (disposed) {

    如果此物体已经丢弃,请勿再次丢弃。

  2. if (disposing) {

    如果以编程方式请求处置( true ),则处置此对象拥有的托管资源(IDisposable对象)。

    如果处理是由垃圾收集器引起的( false ),请不要丢弃托管资源,因为垃圾收集器可能已经处理了所拥有的托管资源,并且会在应用程序终止之前对其进行定义处理。

  3. }

    处置非托管资源并释放对它们的所有引用。 第1步确保只发生一次。

  4. disposed = true

    将此物体标记为防止重复处理。 重复处理可能会在步骤2或3导致NullReferenceException。

问题1
根本不要将它们DisposeDispose方法中。 如果您处理了该类的多个实例会发生什么? 你每次都要处理静态成员,尽管它们已被处理掉了。 我找到的解决方案是处理AppDomain.DomainUnloaded事件并在那里执行静态处理。

问题2
这完全取决于集合的项目是管理还是非管理。 可能值得创建托管包装器,为您正在使用的任何非托管类实现IDisposable,确保管理所有对象。

问题3
IDisposable是一个托管接口。 如果一个类实现了IDisposable,那么它就是一个托管类。 处理内部的托管对象if (disposing) 。 如果它没有实现IDisposable,它既可以被管理也可以不需要处理,或者不受管理,应该在if (disposing)之外if (disposing)

问题4
如果应用程序意外终止,或者不使用手动处理,则垃圾收集器会以随机顺序处理所有对象。 子对象可以在其父母被处置之前被处置,从而导致孩子被父母第二次处置。 大多数托管对象可以安全地多次处理,但前提是它们已经正确构建。 如果对象被多次丢弃,您可能会冒险(但不太可能)导致gargabe集合失败。

如果我有静态事件处理程序导致内存泄漏我应该在哪里取消注册? 进出if(处理)条款?

在实例上调用Dispose方法,其中在类级别使用静态事件处理程序。 所以你不应该在处理中取消注册。 通常静态事件处理程序应该在类卸载时取消注册,或者在应用程序执行期间的某个时刻导出不再需要此事件处理程序。

对于所有管理和未管理的资源,更好地实现IDisposable模式。 请参阅此处http://msdn.microsoft.com/en-us/library/fs2xkftw%28VS.80%29.aspx并在C#中完成/处置模式

听起来你主要有托管对象,即实现IDisposable的对象。 非托管代码将是IntPtr或句柄之类的东西,通常意味着调用非托管代码或P / Invoke来访问它们。

  1. 正如马赫普指出的那样,Dispose并不意味着这一点。 当一个对象完成接收事件时,它应该注销它自己。 如果这不可能,请考虑使用WeakReferences。

  2. 这可能不应该处置,除非这些集合包含需要处置的对象。 如果它们是一次性物品,那么它应该进入“if disposing”块并且您应该对集合中的每个项目调用dispose。

  3. 如果它实现了IDisposable,那么它就是托管的

  4. 在终结器调用时,您不应该访问其他托管代码对象,这是“if(disposing)”块之外的意思。

除非一个类的唯一目的是封装一些资源(*),如果放弃需要清理它,它不应该有一个终结器,并且Dispose(bool)永远不应该被调用值为False,而是调用Dispose (假)应该没有效果。 如果遗留的类需要保留需要清理的资源(如果放弃),它应该将该资源封装到专门用于该目的的对象中。 这样,如果主对象被放弃并且没有其他人持有对封装资源的对象的任何引用,则该对象可以执行其清理而无需使主对象保持活动以进行额外的GC循环。

顺便说一句,我不喜欢微软处理Disposed旗帜。 我建议非虚拟Dispose方法应该使用带有Interlocked.Exchange的整数标志,以确保从多个线程调用Dispose只会导致dispose逻辑执行一次。 标志本身可能是私有的,但应该有一个受保护和/或公共的Disposed属性,以避免要求每个派生类实现自己的处置标志。

(*)资源不是某种特定类型的实体,而是一个松散的术语,它包含一个类可能要求某些外部实体代表它做的任何事情,而外部实体需要被告知停止这样做。 最典型的情况是,外部实体将授予该类独占使用的某些东西(无论是内存区域,锁定,GDI句柄,文件,套接字,USB设备等等),但在某些情况下外部实体可能已经要求实体肯定地做某事(例如,每次发生事件时运行事件处理程序)或保留某些东西(例如,线程静态对象引用)。 “非托管”资源是放弃后不会被清理的资源。

顺便说一句,请注意虽然微软可能已经打算封装非托管资源的对象如果放弃就应该清理它们,但有些类型的资源实际上并不实用。 例如,考虑一个在线程静态字段中存储对象引用的对象,并在Dispose’d时将该对象引用置空(当然,必须在创建对象的线程上进行处理)。 如果对象被放弃但线程仍然存在(例如在线程池中),则线程静态引用的目标可以很容易地无限期地保持活动。 即使没有对被放弃对象的任何引用,因此它的Finalize()方法运行,被放弃的对象也很难找到并销毁某个线程中的线程静态引用。

什么应该进去’如果(处置)’

所有托管对象都应该进入if(disposing)子句。 托管对象不应该在它旁边(它将通过最终化执行)。

原因是垃圾收集器完成过程可以执行Dispose(false),如果该类具有Destructor。 通常只有在存在非托管资源的情况下才会有析构函数。垃圾收集器的终结没有特定的顺序来执行Finalize方法。 因此,在最终确定发生时,其他托管对象可能不在内存中。