C# event/delegate 機制與 Java 觀察者模式(Observer/Observable)同為「發布-訂閱」模式的實現,但前者為語言內建關鍵字,後者為設計模式+介面。本文透過完整代碼對比,深度解析兩者在結構、語法、執行緒安全、記憶體管理等面向的異同。
// 1. 定義委派型別(相當於觀察者介面的 Update 方法簽名)
public delegate void ChangedEventHandler(object sender, EventArgs e);
// 2. 主題類別(Subject / Observable)
public class ListWithChangedEvent : ArrayList
{
// 事件聲明(編譯器自動產生 add/remove/raise 方法)
public event ChangedEventHandler Changed;
// 觸發方法(受保護虛擬,允許衍生類別擴展)
protected virtual void OnChanged(EventArgs e)
{
Changed?.Invoke(this, e); // Null-conditional 安全觸發
}
// 覆寫會改變集合的方法,操作後觸發事件
public override int Add(object value)
{
int i = base.Add(value);
OnChanged(EventArgs.Empty);
return i;
}
public override void Clear() { base.Clear(); OnChanged(EventArgs.Empty); }
public override object this[int index]
{
set { base[index] = value; OnChanged(EventArgs.Empty); }
}
}
// 3. 觀察者類別(Observer / Listener)
public class EventListener
{
private ListWithChangedEvent _list;
public EventListener(ListWithChangedEvent list)
{
_list = list;
// 訂閱(相當於 AddObserver)
_list.Changed += ListChanged;
}
private void ListChanged(object sender, EventArgs e)
{
Console.WriteLine("Event fired: list changed");
Thread.Sleep(5000); // 模擬耗時處理
}
public void Detach()
{
// 退訂(相當於 RemoveObserver)
_list.Changed -= ListChanged;
_list = null;
}
}
// 4. 測試程式
public class Test
{
public static void Main()
{
var list = new ListWithChangedEvent(); // 建立 Subject
var listener = new EventListener(list); // 建立 Observer 並訂閱
list.Add("item 1"); // 觸發事件
list.Clear(); // 再次觸發
listener.Detach(); // 移除觀察者
}
}
// Java 介面定義
interface Observer { void Update(Observable o, Object arg); }
interface Observable { void AddObserver(Observer o); void RemoveObserver(Observer o); void NotifyObservers(); }
// 主題實作
class ListWithChangedEvent extends Observable {
public void Add(Object value) {
super.add(value);
setChanged();
notifyObservers(); // 遍歷觀察者呼叫 Update
}
}
// 觀察者實作
class EventListener implements Observer {
public void Update(Observable o, Object arg) {
System.out.println("List changed");
}
}
| 面向 | C# Event/Delegate | Java Observer Pattern |
|---|---|---|
| 語言支援 | 關鍵字 event/delegate,編譯器產生訂閱/發佈邏輯 | 純介面+手動實作,無語言層級支援 |
| 型別安全 | 委派簽名強型別,編譯期檢查 | Object 參數需轉型,執行期風險 |
| 多播支援 | 原生支援 +=/-= 多播委派 | 需自行維護 List<Observer> |
| 語法簡潔度 | obj.Event += Handler; 一行訂閱 | subject.addObserver(observer) 需實作介面類別 |
| 記憶體洩漏風險 | 事件持有委派參考,若不退訂會阻礙 GC(需 -= 或弱事件模式) | 同樣需 removeObserver,否則 Observable 持有強參考 |
| 執行緒安全 | 事件觸發非原子,需自行鎖定或用 Interlocked 模式 | 同樣非原子,Observable 內部同步僅保護列表結構 |
| 延遲/非同步 | 配合 Task.Run/BeginInvoke 易於實現 | 需自行建立執行緒池或用 ExecutorService |
| 序列化 | 事件欄位預設不序列化 ([field:NonSerialized]) | 需自行標記 transient |
public event EventHandler<CustomEventArgs> CustomEvent;
protected virtual void OnCustomEvent(CustomEventArgs e)
=> CustomEvent?.Invoke(this, e);
解決「觀察者忘記退訂導致主題無法 GC」問題,WPF/UWP 內建支援。
public event Func<Task> AsyncEvent;
public async Task TriggerAsync()
{
var tasks = AsyncEvent?.GetInvocationList()
.Cast<Func<Task>>()
.Select(f => f()) ?? Array.Empty<Task>();
await Task.WhenAll(tasks);
}
C# event/delegate 以語言內建機制實現了觀察者模式的所有核心能力,並提供:
Object 參數+=/-=/?.Invoke() 三大操作Java 觀察者模式雖具跨語言可移植性,但在 JVM 生態外無優勢。C# 開發者應優先使用 event,僅在跨進程、跨語言、或需精細控制訂閱生命週期時考慮手動實作觀察者介面。