我一直在阅读有关内存管理的内容,并且在一个项目中遇到过这样的情况,在这个项目中,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在内存中停留的时间。如果我忽视了任何事情,请告诉我。
答案 0 :(得分:2)
IDisposable
用于确定性地释放非托管资源。
无需删除事件处理程序。例如,如果您查看Windows窗体Form
和UserControl
类,或ASP.NET Page
和UserControl
类,所有这些类都是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();})但这看起来很尴尬。