寫下 source.StateChanged += observer.Handler 時,source 內部持有 observer 的強參考 (委派的 Target)。若 source 生命週期較長 (如單例、靜態事件、UI 視覺樹根節點),而 observer 應該被回收時,GC 無法回收 observer,造成:
source.StateChanged -= observer.Handler 需開發者記住在正確時機 (如 Dispose、Unloaded) 調用。現實中極易遺漏、時機難把控、或因例外導致清理碼不執行。
核心思想:**訂閱時不持有強參考,改持有 WeakReference;當目標被 GC 回收時,自動從事件源移除訂閱。**
提供兩種使用風格:
observed.SomeEvent += WeakEventHandlerFactory.MakeWeak(
observer.SomeEventHandler,
(handler, param) => observed.SomeEvent -= handler, // 移除委派
null // 額外參數
);
observed.SomeEvent += WeakEventHandlerFactory.MakeWeak2(
observer.SomeEventHandler,
observed, // 事件源
"SomeEvent" // 事件名稱
);
public static class WeakEventHandlerFactory {
// 入口:建立弱委派
public static EventHandler<E> MakeWeak<E>(EventHandler<E> handler,
UnregisterCallback<E> unregister, object param) where E : EventArgs { ... }
// 內部弱處理器
private class WeakEventHandler<T, E> : IWeakEventHandler<E>
where T : class where E : EventArgs {
private readonly WeakReference _weakReference; // 弱參考目標
private readonly OpenEventHandler _openHandler; // 開放委派 (無 Target)
private UnregisterCallback<E> _unregister; // 移除回呼
private readonly object _param;
public WeakEventHandler(EventHandler<E> handler, ...) {
_weakReference = new WeakReference(handler.Target);
// 建立開放委派:static 方法簽名 (T @this, object sender, E e)
_openHandler = (OpenEventHandler)Delegate.CreateDelegate(
typeof(OpenEventHandler), null, handler.Method);
Handler = Invoke; // 公開給事件源的強委派
}
private void Invoke(object sender, E e) {
var target = (T)_weakReference.Target;
if (target != null) {
_openHandler.Invoke(target, sender, e); // 正常調用
} else if (_unregister != null) {
_unregister(Handler, _param); // 目標已死,自動移除訂閱
_unregister = null; // 防重複
}
}
}
}
Delegate.CreateDelegate 建立不綁定 Target 的委派,調用時手動傳入 target,避免閉包捕獲強參考。typeof(WeakEventHandler<,>).MakeGenericType(...) 動態建構對應觀察者類型的泛型實例。(EventHandler<E> handler, object param),負責從事件源移除 handler。若傳 null,事件源仍殘留一個輕量委派 (指向已死的弱處理器),但不阻礙 GC,且後續觸發時弱處理器會自動清理自身。IDisposable 確保移除。WeakEventManager (WPF)、System.ComponentModel.INotifyPropertyChanged 擴充套件、Rx.NET WeakReference 操作符。事件訂閱是 .NET 記憶體洩漏的頭號隱形殺手。WeakEventHandlerFactory 以約 100 行程式碼封裝了「弱參考 + 自動移除」的完整模式,讓開發者以極低成本消除此類風險。掌握其原理 (WeakReference、開放委派、生命週期攔截),更能舉一反三,處理其他委派/回呼場景的循環參考問題。