有两种方法(我知道)会在C#中导致无意的内存泄漏:
IDisposable
我真的不明白第二点。如果源对象的生命周期比侦听器长,并且当没有其他引用时,侦听器不再需要事件,则使用普通的.NET事件会导致内存泄漏:源对象将侦听器对象保存在内存中应该是垃圾收集。
你能用C#中的代码解释事件如何导致内存泄漏,以及如何使用弱引用和没有弱引用来编写代码来解决它?
答案 0 :(得分:34)
当侦听器将事件侦听器附加到事件时,源对象将获得对侦听器对象的引用。这意味着在分离事件处理程序或收集源对象之前,垃圾收集器无法收集侦听器。
考虑以下课程:
class Source
{
public event EventHandler SomeEvent;
}
class Listener
{
public Listener(Source source)
{
// attach an event listner; this adds a reference to the
// source_SomeEvent method in this instance to the invocation list
// of SomeEvent in source
source.SomeEvent += new EventHandler(source_SomeEvent);
}
void source_SomeEvent(object sender, EventArgs e)
{
// whatever
}
}
...然后是以下代码:
Source newSource = new Source();
Listener listener = new Listener(newSource);
listener = null;
即使我们将null
分配给listener
,它也没有资格进行垃圾回收,因为newSource
仍然持有对事件处理程序的引用(Listener.source_SomeEvent
) 。要解决此类泄漏问题,在不再需要事件侦听器时始终分离事件监听器非常重要。
编写上述示例以关注泄漏问题。为了修复该代码,最简单的方法可能是让Listener
保持对Source
的引用,以便以后可以分离事件监听器:
class Listener
{
private Source _source;
public Listener(Source source)
{
_source = source;
// attach an event listner; this adds a reference to the
// source_SomeEvent method in this instance to the invocation list
// of SomeEvent in source
_source.SomeEvent += source_SomeEvent;
}
void source_SomeEvent(object sender, EventArgs e)
{
// whatever
}
public void Close()
{
if (_source != null)
{
// detach event handler
_source.SomeEvent -= source_SomeEvent;
_source = null;
}
}
}
然后调用代码可以发信号通知它是使用该对象完成的,这将删除Source
对'Listener`的引用;
Source newSource = new Source();
Listener listener = new Listener(newSource);
// use listener
listener.Close();
listener = null;
答案 1 :(得分:12)
阅读Jon Skeet关于事件的优秀article。这不是传统意义上的真正的“内存泄漏”,而是更多的未被断开的保持引用。因此,请始终记住-=
前一点+=
的事件处理程序,您应该是金色的。
答案 2 :(得分:2)
严格来说,在托管的.NET项目的“沙箱”中没有“内存泄漏”;只有开发人员认为必要的参考时间才会更长。弗雷德里克有权利;当您将处理程序附加到事件时,因为处理程序通常是实例方法(需要实例),所以只要维护此引用,包含侦听器的类的实例就会保留在内存中。如果侦听器实例依次包含对其他类的引用(例如,对包含对象的反向引用),则在侦听器退出所有其他范围之后,堆可以保持很长时间。
也许对委托和MulticastDelegate有更多深奥知识的人可以对此有所了解。我所看到的方式,如果以下所有情况都属实,可能会出现真正的泄漏:
我从来没有听说过任何涉及在委托目标上调用Dispose()的最佳实践,更不用说事件监听器了,所以我只能假设.NET开发人员知道他们在这种情况下做了什么。如果这是真的,并且事件后面的MulticastDelegate尝试正确处理侦听器,那么所有必要的是在需要处理的侦听类上正确实现IDisposable。