问题:已注册的事件处理程序将事件引用创建为事件处理程序的实例。如果该实例无法注销事件处理程序(大概是通过Dispose),那么实例内存将不会被垃圾收集器释放。使用WeakReference解决导致内存泄漏的.NET未注册事件处理程序的问题
例子:
class Foo
{
public event Action AnEvent;
public void DoEvent()
{
if (AnEvent != null)
AnEvent();
}
}
class Bar
{
public Bar(Foo l)
{
l.AnEvent += l_AnEvent;
}
void l_AnEvent()
{
}
}
如果我实例化一个Foo,并通过这一个新的酒吧构造,然后让Bar对象的旅途中,它不会被垃圾收集器释放,因为AnEvent登记。
我认为这是内存泄漏,看起来就像我的旧C++日子。当然,我可以让Bar IDisposable,在Dispose()方法中注销事件,并确保在其实例上调用Dispose(),但为什么我必须这样做?
我第一个问题,为什么事件与强引用?为什么不使用弱引用?事件用于抽象地通知对象另一个对象的变化。在我看来,如果事件处理程序的实例不再被使用(即没有对事件的非事件引用),那么它所注册的任何事件都应该自动取消注册。我错过了什么?
我看过WeakEventManager。哇,真是痛苦。使用起来不仅非常困难,而且其文档也不够充分(请参阅http://msdn.microsoft.com/en-us/library/system.windows.weakeventmanager.aspx - 注意到“继承者注释”部分,其中有6个模糊的项目符号)。
我在不同的地方看过其他讨论,但没有什么我觉得我可以使用的。如下所述,我提出了一个基于WeakReference的更简单的解决方案。我的问题是:这是否不符合要求,复杂程度要低得多?
class Foo
{
public WeakReferenceEvent AnEvent = new WeakReferenceEvent();
internal void DoEvent()
{
AnEvent.Invoke();
}
}
class Bar
{
public Bar(Foo l)
{
l.AnEvent += l_AnEvent;
}
void l_AnEvent()
{
}
}
通知两件事情::
要使用的解决方案,如下上述代码被修改 1. Foo类被修改以两种方式:事件被替换WeakReferenceEvent的一个实例,如下所示;并且事件的调用被改变。 2. Bar类是UNCHANGED。
无需子类WeakEventManager的,实施IWeakEventListener等
好了,到WeakReferenceEvent实施。这显示在这里。需要注意的是它采用了通用的WeakReference <牛逼>,我从这里借:http://damieng.com/blog/2006/08/01/implementingweakreferencet
class WeakReferenceEvent
{
public static WeakReferenceEvent operator +(WeakReferenceEvent wre, Action handler)
{
wre._delegates.Add(new WeakReference<Action>(handler));
return wre;
}
List<WeakReference<Action>> _delegates = new List<WeakReference<Action>>();
internal void Invoke()
{
List<WeakReference<Action>> toRemove = null;
foreach (var del in _delegates)
{
if (del.IsAlive)
del.Target();
else
{
if (toRemove == null)
toRemove = new List<WeakReference<Action>>();
toRemove.Add(del);
}
}
if (toRemove != null)
foreach (var del in toRemove)
_delegates.Remove(del);
}
}
它的功能实在是微不足道。我重写operator +来获得+ =语法糖匹配事件。这会为Action委托创建WeakReferences。这允许垃圾收集器释放事件目标对象(在此示例中为Bar),当没有人持有该对象时。
在Invoke()方法中,只需运行弱引用并调用其目标操作即可。如果发现任何死亡(即垃圾收集)参考,请将其从列表中删除。
当然,这只适用于Action类型的代表。我试图做出这个通用的,但跑到失踪的地方T:委托在C#!
作为替代方案,只需修改类WeakReferenceEvent是一个WeakReferenceEvent <Ť>,和替换动作<Ť>中的作用。修复编译器错误,并且用户可以像这样被使用的类:
class Foo
{
public WeakReferenceEvent<int> AnEvent = new WeakReferenceEvent<int>();
internal void DoEvent()
{
AnEvent.Invoke(5);
}
}
与<牛逼>,和运营商的完整代码 - (去除事件)如下所示:
class WeakReferenceEvent<T>
{
public static WeakReferenceEvent<T> operator +(WeakReferenceEvent<T> wre, Action<T> handler)
{
wre.Add(handler);
return wre;
}
private void Add(Action<T> handler)
{
foreach (var del in _delegates)
if (del.Target == handler)
return;
_delegates.Add(new WeakReference<Action<T>>(handler));
}
public static WeakReferenceEvent<T> operator -(WeakReferenceEvent<T> wre, Action<T> handler)
{
wre.Remove(handler);
return wre;
}
private void Remove(Action<T> handler)
{
foreach (var del in _delegates)
if (del.Target == handler)
{
_delegates.Remove(del);
return;
}
}
List<WeakReference<Action<T>>> _delegates = new List<WeakReference<Action<T>>>();
internal void Invoke(T arg)
{
List<WeakReference<Action<T>>> toRemove = null;
foreach (var del in _delegates)
{
if (del.IsAlive)
del.Target(arg);
else
{
if (toRemove == null)
toRemove = new List<WeakReference<Action<T>>>();
toRemove.Add(del);
}
}
if (toRemove != null)
foreach (var del in toRemove)
_delegates.Remove(del);
}
}
希望这会帮助别人,当他们遇到神秘事件导致垃圾收集世界的内存泄漏!
“我第一个问题是为什么事件用强引用来实现?” - 在某些情况下,只有通过事件引用保持活动状态的中间对象以及发布者与订户之间的事件调用是非常有用的。例如,当您为每个事件源需要单独的实例时,这是一种有用的模式,并且您不知道在任何时间点将有多少事件源。 – 2010-05-11 23:09:22
有用的文章,但没有太多的问题!你有没有考虑过这个博客呢? – 2010-05-11 23:15:51
@Franci:好点。这是例外情况,与正常情况相反,你不觉得吗?我已经使用了相当多的事件,并且尚未需要此功能。 – Eric 2010-05-11 23:19:30