概要
Prismライブラリ(バージョン8.x以降)に組み込まれているイベント集約機構「EventAggregator」の内部実装について掘り下げる。本項では、特にイベント購読を管理するための基盤となるクラス群と、メモリ管理に関わるデリゲートの参照方式に焦点を当てて解説する。
IEventSubscriptionインターフェースの役割
EventAggregatorの中核を成すPubSubEventは、内部にIEventSubscriptionのコレクションを保持している。このインターフェースは、イベントの購読情報を抽象化するものであり、以下の2つの重要なメンバを定義している。
- 識別子: 購読を一意に識別するためのトークン
- 実行戦略の取得: イベント発行時に実行されるデリゲートを取得するメソッド
実行戦略を取得するメソッドは、イベントがパブリッシュされた際にどの処理を呼び出すかを決定する重要な役割を持つ。このインターフェースの基本実装としてEventSubscriptionクラスが用意されている。
ISubscriptionEntry インターフェースの再構築例
public interface ISubscriptionEntry
{
Guid EntryId { get; set; }
Action<object[]> BuildExecutionAction();
}
EventSubscriptionとデリゲートの間接参照
EventSubscriptionは、実際のアクション(Action)を直接保持するのではなく、IDelegateReferenceというインターフェースを通じて間接的に参照する設計になっている。この設計の根底には、メモリリークを防ぐための意図がある。
DelegateReferenceクラスは、デリゲートの参照方法を制御する。コンストラクタでholdStrongReferenceという真偽値を受け取り、これに応じて強参照または弱参照(WeakReference)のいずれかを使い分ける。
- holdStrongReferenceがtrueの場合: デリゲートを直接保持し、ガベージコレクション(GC)の対象外とする。
- holdStrongReferenceがfalseの場合: デリゲートのターゲットオブジェクトを
WeakReferenceでラップし、メソッド情報と型情報を保存する。ターゲットがGCによって回収された場合、実行戦略の構築時にnullを返すことで、無効な購読を安全に無視できる。
MethodProxy クラスの再構築例(DelegateReferenceの代替実装)
public class MethodProxy : IMethodProxy
{
private readonly Delegate _strongRef;
private readonly WeakReference _weakRef;
private readonly MethodInfo _methodInfo;
private readonly Type _delegateType;
public MethodProxy(Delegate targetDelegate, bool holdStrongReference)
{
if (targetDelegate == null) throw new ArgumentNullException(nameof(targetDelegate));
if (holdStrongReference)
{
_strongRef = targetDelegate;
}
else
{
_weakRef = new WeakReference(targetDelegate.Target);
_methodInfo = targetDelegate.GetMethodInfo();
_delegateType = targetDelegate.GetType();
}
}
public Delegate GetTarget()
{
return _strongRef ?? ReconstructDelegate();
}
private Delegate ReconstructDelegate()
{
if (_methodInfo.IsStatic) return _methodInfo.CreateDelegate(_delegateType, null);
var targetObj = _weakRef.Target;
return targetObj != null ? _methodInfo.CreateDelegate(_delegateType, targetObj) : null;
}
}
EventSubscriptionのGetExecutionStrategy(再構築例ではBuildExecutionAction)は、この間接参照からアクションを取り出し、実行用のデリゲートを生成する。
SubscriptionEntry クラスの再構築例(EventSubscriptionの代替実装)
public class SubscriptionEntry : ISubscriptionEntry
{
private readonly IMethodProxy _callbackProxy;
public SubscriptionEntry(IMethodProxy callbackProxy)
{
_callbackProxy = callbackProxy ?? throw new ArgumentNullException(nameof(callbackProxy));
if (!(_callbackProxy.GetTarget() is Action))
throw new ArgumentException("Invalid callback type");
}
public Action CallbackAction => (Action)_callbackProxy.GetTarget();
public Guid EntryId { get; set; }
public virtual Action<object[]> BuildExecutionAction()
{
var currentAction = CallbackAction;
if (currentAction == null) return null;
return _ => RunCallback(currentAction);
}
protected virtual void RunCallback(Action action)
{
if (action == null) throw new ArgumentNullException(nameof(action));
action();
}
}
スレッド制御を実現する派生クラス
イベント購読時のスレッド実行コンテキストを制御するために、EventSubscription(上記SubscriptionEntry)の仮想メソッドInvokeAction(上記RunCallback)をオーバーライドした2つの派生クラスが存在する。
BackgroundEventSubscription
このクラスは、購読したアクションをバックグラウンドスレッドで非同期実行するために使用される。RunCallbackメソッドをオーバーライドし、内部でTask.Run等を用いてデリゲートをスレッドプールに投入する。
protected override void RunCallback(Action action)
{
Task.Run(() => action());
}
DispatcherEventSubscription
このクラスは、UIスレッドなど特定の同期コンテキスト上でアクションを実行するために使用される。保持しているSynchronizationContextのPostメソッドを呼び出すことで、呼び出し元のコンテキストにマーシャリングしてデリゲートを実行する。
private readonly SynchronizationContext _syncContext;
protected override void RunCallback(Action action)
{
_syncContext.Post(_ => action(), null);
}
これらの基底クラスおよび派生クラスの構造により、Prismのイベントシステムは購読メソッドのメモリ管理とスレッドアフィニティを柔軟に制御できる基盤を備えている。