Singleton across AppDomain
Javaでは安易に使われすぎたせいかDIやTDDがもてはやされる昨今ではややもすれば後ろ指を差されるSingletonパターンだがインスタンス数を完全に制御したい場合やリソースへのアクセスを制限したい場合には相変わらずよく使われるし使いやすいのも確か。
.NETの場合AppDomainはCLR上では一つとは限らないしJavaのクラスローダのように階層構造も取らないため厳密な意味でSingletonを使いたい場合には以下のような一般的なスタティックイディオムは使えない。
よくあるスタティックイディオムによるSingletonの取得
public class SingletonObj { private static SingletonObj instance = new SingletonObj(); public static SingletonObj GetInstance() { return instance; } private SingletonObj(){省略} }
スタティックなフィールドは異なるAppDomain間では共有されない為上記のGetInstance()メソッドは異なるAppDomainで使用された場合は別なオブジェクトへの参照を返す。
なので.NETでAppDomainを越えた所で(つまりCLR上で)厳密なSingletonを使用する場合はどうするか? この最良解が見つからないのでとりあえず現状は.NET Remotingを使用している。
.NET Remoting上でアクティベートする為にロードされる型はAppDomainが幾つ存在しようと一つの型から生成される事を保証されるので以下のようなイディオムでSingletonを取得する事ができる(オブジェクトの排他に関しては省略している)
public IRemotingExposable GetRemotingObj(Type type, string url) { IRemotingExposable test = Activator.GetObject(type, url) as IRemotingExposable; try { if (test != null && test.IsAlivedOnRemote) { return test; } else { return {SingletonモードでのSAO(Server Activate Object)生成&登録処理}; } } catch { return {SingletonモードでのSAO(Server Activate Object)生成&登録処理}; } }
このメソッドで取得されるIRemotingExposableだがインタフェースを実装する事以外に.NET Remotingのお約束として必ずMarshalByRefObjectを継承する必要がある。
注意しなくてはならないのはActivator#GetObjectで取得したオブジェクトの型を調べる際には一般的なisやasは役に立たないという事だ。というのもActivatorで取得されるMarshalByRefObjectのインスタンスは必ず透過プロキシへの参照として戻るので型チェックの意味が無い(必ずtrueになる)のである。
ここで使用しているIRemotingExposableインタフェースはその為に用意したインタフェースでありこの例で言うとIsAlivedOnRemoteメソッドをコールする事により初めて実プロキシ経由でRemotingのオブジェクトへのアクセスが発生するからだ。
ここで凄く嫌なのは.NET Remotingの世界において本当にその型のオブジェクトが生成されているかどうかを検査するには実際にそのオブジェクト固有のメソッドやプロパティにアクセスしてみないと解らないという事だ。上記のイディオムの場合、一番最初にRemotingオブジェクトが生成されるのはIsAlivedOnRemoteメソッドが実行された結果として例外が発生する時である。
例外はあくまで「例外」として処理したいがRemotingの場合は他に方法が無い(というか私はこの方法以外知らない)のでこうするしかない。
せめてasやis演算子の振る舞いを拡張するか、又はRemotingに対応した型チェック方法を提供してくれれば良いのになぁと思う。
って実は凄くスマートなやり方があったりするのだろうか?