在实现IDisposable的类中如何处理委托/事件引用



我一直在阅读关于内存管理的文章,在一个项目中遇到了这样的情况:这本书和谷歌都没有给出确切的答案。我已经知道委托是管理对象,事件是委托实例。话虽如此,一旦应用程序结束,委派实例将从内存中删除。

我想不出的是,如何确保外部代码在我的类被处理(显式或GC)时释放了所有事件引用。例如,类A公开一个事件,而类B使用它。类B在不释放对委托的引用的情况下调用类A上的Dispose。当然,我们不能从Dispose方法本身抛出错误。

下面是一个有委托的类和另一个使用它的类

public class ClassB
{
private ClassA A { get; set; }
public ClassB()
{
this.A = new ClassA();
this.A.OnProcessed += new ClassA.DelegateProcessed(this.ClassA_Processed);
}
public void Process()
{
this.A.Process();
}
public void ClassA_Processed (ClassA sender, EventArgs e)
{
// Do something.
// Code written by another developer does not free up events before calling Dispose.
this.A.Dispose();
this.A = null;
}
}
public class ClassA: IDisposable
{
public delegate void DelegateProcessed (A sender, EventArgs e);
public event DelegateProcessed OnProcessed = null;
~ClassA() { this.Dispose(false); }
public void Dispose ()
{
this.Dispose(true);
System.GC.SuppressFinalize(this);
}
private void Dispose (bool disposing)
{
if (!this.Disposed)
{
if (disposing)
{
// Dispose managed resources here.
// Is it possible / advisable to dispose of delegates / events here?
// Will this adversely affect the consumer class?
this.OnProcessed -= new ClassA.DelegateProcessed(this.ClassA_Processed);
}
}
this.Disposed = true;
}
public void Process () { this.OnProcessed(this, new EventArgs()); }
public void ClassA_Processed (ClassA sender, EventArgs e) { }
}

重点是确保无论开发人员如何处理ClassB,ClassA都有资格进行垃圾收集。重点是尽量减少ClassA在内存中花费的时间,即使消费者很粗心。

UPDATE:从答案中可以清楚地看出,事件不必从ClassA中显式删除。至于主要问题,弱参考文献似乎是解决问题的方法,如下所述。目标是尽量减少ClassA在内存中停留的时间。请告诉我,以防我忽略了任何事情。

IDisposable用于确定释放非托管资源。

没有必要删除事件处理程序。例如,如果您查看Windows窗体FormUserControl类,或ASP.NETPageUserControl类,它们都是IDisposable,您将看到事件的广泛使用,并且在处理过程中没有特殊处理。

您应该了解弱事件模式,而不是"经典"事件订阅。

事件订阅可能会使对象保持活动状态,即使这些引用是唯一剩下的引用,并且被引用的对象本身已经超出范围。在这种情况下,GarbageCollector将永远不会收集引用的对象,并在应用程序结束之前保持活动状态。

这会导致严重的内存泄漏。

如果使用弱事件模式,则可以让GabageCollector更好地确定对象是否仍被引用,或者事件是否是唯一的引用。在这种情况下,对象被收集起来,您的资源被释放。

这部分代码:

private ClassA A { get; set; }
public ClassB()
{
this.A = new ClassA();
this.A.OnProcessed += new ClassA.DelegateProcessed(this.ClassA_Processed);
}

意味着你什么都不做。

B实例拥有A实例,并且A具有对B的再次引用(通过事件)。

B变得不可访问时,A也将被收集(GC和循环引用)。

当"A"在B之前被处置(长)时,"A"也将被收集(方向性)。

A上的IDispoable接口毫无意义。


关于实施:

// class B
this.A.OnProcessed += new ClassA.DelegateProcessed(this.ClassA_Processed);
// in classA
this.OnProcessed -= new ClassA.DelegateProcessed(this.ClassA_Processed);

这是行不通的,两个不同的this意味着它们是两种不同的方法。

一个编写正确的类应该在其IDisposable.Dispose方法中取消订阅它订阅的任何事件。如果订阅了事件的对象的GC生存期与订阅对象的有用生存期相当(这是一种非常常见的情况),那么订阅是被清理还是被挂起都无关紧要。不幸的是,如果A在没有从B的事件中取消订阅的情况下被放弃,并且某个对象保持对B的长期引用(有意或无意),那么保持B活动的任何对象也将保持AA直接或间接引用的任何对象(包括从A具有活动事件订阅的对象)的活动。最终很容易形成大型互连对象林,这些对象通常有资格进行垃圾收集,但只要需要任何,所有都必须保持活动状态。

太糟糕了,活动订阅和取消订阅太尴尬了。如果存在与事件相关联的对象类型,要订阅各种事件的对象可以使用"事件管理器"对象来管理订阅(因此,可以说MyEventManager.Subscribe(SomeObject.SomeEvent, someProc)之类的话,然后让MyEventManager.Dispose取消订阅它已建立订阅的所有事件。不幸的是,没有合适的方法来让方法接受事件作为参数,因此也没有办法有一个通用类来管理传入订阅。最好的方法可能是有一个CleanupManager类,它需要一对的委托,并被调用类似于`MyCleaner.Register(()=>{SomeObject.SomeEvent+=someProc;},()=>{SomeObject.SomeEvent-=someProc();}),但这似乎相当尴尬。

最新更新