問題:已註冊的事件處理程序將事件引用創建爲事件處理程序的實例。如果該實例無法註銷事件處理程序(大概是通過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