C# ドメインイベントの実装 つづき
前回のエントリーの続きから
nomoredeathmarch.hatenablog.com
Publisherクラスのメソッドについて記述
まずSubscribeメソッドから
public static void Subscribe(ISubscriber subscriber) { try { Mutex.WaitOne(); if (subscriber == null) throw new ArgumentNullException("subscriber"); if (Subscribers.Contains(new WeakReference<ISubscriber>(subscriber))) return; Subscribers.Add(new WeakReference<ISubscriber>(subscriber)); } finally { Mutex.ReleaseMutex(); } }
・イベントの通知を予約するためのメソッド
・Mutexクラスを使って排他制御(finallyで解放を忘れずに)
・イベントの多重処理防止のため登録済みのサブスクライバだったら処理を抜ける。
・サブスクライバの弱参照をリストに追加する。
続いてUnsubscribeメソッド
public static void Unsubscribe(ISubscriber subscriber) { try { Mutex.WaitOne(); if (subscriber == null) throw new ArgumentNullException("subscriber"); Subscribers.RemoveAll(x => subscriber.Equals(ToStrong(x))); } finally { Mutex.ReleaseMutex(); } }
・イベントの通知予約を解除するメソッド。
・Subscribeメソッドと同様にMutexで排他制御
・リストからの削除は強い参照に変換して対象を比較
続いてToStrongメソッド
private static ISubscriber ToStrong(WeakReference<ISubscriber> obj) { ISubscriber ret; obj.TryGetTarget(out ret); return ret; }
・弱参照を強参照に変換して返すメソッド。
・クラス内の他のメソッド内で呼び出されるprivateなメソッド。
最後にPublishメソッド
public static void Publish(IEvent e) { try { Mutex.WaitOne(); if (e == null) throw new ArgumentNullException("e"); var list = new List<ISubscriber>(); list.AddRange(Subscribers.ConvertAll<ISubscriber>(x => ToStrong(x))); list.RemoveAll(x => x == null || !x.CanHandle(e)); list.ForEach(x => x.Handle(e)); Subscribers.RemoveAll(x => ToStrong(x) == null); } finally { Mutex.ReleaseMutex(); } }
・受け取ったイベントをサブスクライバに通知する。
・ここでもやっぱりMutexで排他制御
・弱参照のリストを強参照に変換、変換出来なった要素やCanHandleメソッドでFalseが返る要素をリストから除外
・ForEachメソッドで各要素のHandleメソッドにイベントを渡す。
・イベントの通知が終わったらGC回収済みのサブスクライバをリストから除外する。
注意事項
1.排他制御
今回はMutexクラスを使って各Publicメソッドの先頭で排他制御を掛けた。
マルチスレッド周りのプログラムは正直あまり理解していないので辛うじて自分が分かる方法です・・・
2.メモリリーク対策
静的なリストはRemoveなりClearメソッドで中身を消さないと、アプリケーションが動いている限り参照が残り続け、メモリリークを起こす。クライアントコード側でDisposeを実装し、確実にリストから排除する仕組みになっていれば問題ないが、実装漏れが起こるリスクを考えるとPublisherクラス内を弱参照を駆使して実装する方が安全と言える。
3.購読停止の管理
サブスクライバがGCに回収されない限りイベントは通知される。購読が不要になった時点でUnsubscribeメソッドを呼び出す。