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

时间:2012-09-24 13:58:19

标签: c# .net events delegates idisposable

我一直在阅读有关内存管理的内容,并且在一个项目中遇到过这样的情况,在这个项目中,Google和Google都没有提出确切的答案。我已经知道委托是管理对象,事件是委托实例。话虽如此,一旦应用程序结束,代理实例将从内存中删除。

我无法弄清楚的是如何确保外部代码在我的类处理时(显式地或通过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在内存中花费的时间,即使消费者不小心。

更新:从答案中可以清楚地看出,不必从ClassA中明确删除事件。至于主要问题,弱参考似乎是下面回答的方式。目标是最小化ClassA在内存中停留的时间。如果我忽视了任何事情,请告诉我。

4 个答案:

答案 0 :(得分:2)

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

无需删除事件处理程序。例如,如果您查看Windows窗体FormUserControl类,或ASP.NET PageUserControl类,所有这些类都是IDisposable,那么您会看到广泛使用的事件,并且在处置过程中没有特殊处理。

答案 1 :(得分:1)

代码的这一部分:

private ClassA A { get; set; }

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

意味着您必须 nothing

B个实例拥有A个实例,而A又有一个参考(通过该事件)到B

B无法访问时,也会收集A(GC和循环引用)。

当{A}在B之前处置(长)时,也会收集'A'(方向性)。

IDispoable上的A界面毫无意义。


关于实施:

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

 // in classA
   this.OnProcessed -= new ClassA.DelegateProcessed(this.ClassA_Processed);

这不起作用,2个不同this表示它们是2种不同的方法。

答案 2 :(得分:1)

您应该查看Weak Event Patterns,而不是“经典”活动订阅。

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

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

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

答案 3 :(得分:0)

正确编写的类应在其IDisposable.Dispose方法中取消订阅它已订阅的任何事件。如果订阅了事件的对象的GC生命周期与订阅的对象的有效生命周期相当(这是一种非常常见的情况),则订阅是清理还是留下悬挂并不重要。不幸的是,如果A被放弃而没有取消订阅B的事件,并且某些内容保留了对B的长期引用(故意与否),则保留B } alive还将保持A以及A持有直接或间接引用的任何内容(包括具有来自A的活动事件订阅的对象)。很容易得到大型互连对象的森林,这些森林通常有资格进行垃圾收集,但只要任何所有都必须保持活着状态。需要。

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