UnityのLifetimeManager
はシングルトンで管理するContainerControlledLifetimeManager
か、デフォルトの毎回newする‘PerResolveLifetimeManager‘か、スレッド単位のPerThreadLifetimeManager
が用意されています。あとマニアックなところだと、ExternallyControlledLifetimeManager
とかいう弱参照で管理されるものもあります。
今回は、LifetimeManager
を拡張して任意のタイミングで破棄できるLifetimeManager
を作ってみようと思います。
破棄を通知するインターフェースの定義
まず、オブジェクトが破棄されたことを通知するインターフェースを定義します。とりあえず今回はイベントの発火でライフサイクルの終了を通知するようにしてみようと思うので、シンプルにCompleted
イベントだけを持ったインターフェースを定義しました。
interface ITransactionPolicy { event EventHandler Completed; }
ITransactionPolicyを使ったLifetimeManagerの定義
後は、ITransactionPolicy
のCompleted
イベントが起きたらインスタンスを破棄するようにするLifetimeManager
の実装を作るだけです。
sealedclass TransactionLifetimeManager : LifetimeManager, IDisposable { privateobjectvalue; publicoverrideobject GetValue() { returnvalue; } publicoverridevoid RemoveValue() { this.Dispose(); } publicoverridevoid SetValue(object newValue) { value = newValue; var tx = valueas ITransactionPolicy; if (tx != null) { tx.Completed += this.Tx_Completed; } } publicvoid Dispose() { this.Dispose(true); GC.SuppressFinalize(this); } privatevoid Dispose(bool disposable) { if (disposable) { (valueas IDisposable)?.Dispose(); this.value = null; } } privatevoid Tx_Completed(object sender, EventArgs e) { ((ITransactionPolicy)this.value).Completed -= this.Tx_Completed; this.RemoveValue(); } }
使ってみよう
使い方は簡単です。ITransactionPolicy
を実装してTransactionLifetimeManager
で登録するだけ。
// こっちはライフサイクルを制御するclass Person : ITransactionPolicy, IDisposable { private Service Service { get; } public Person(Service service) { this.Service = service; Console.WriteLine("Person#Constructor"); } publicevent EventHandler Completed; publicvoid Complete() { this.Completed?.Invoke(this, EventArgs.Empty); } publicvoid Dispose() { Console.WriteLine("Person#Dispose"); } } // こっちは普通のクラスclass Service { public Service() { Console.WriteLine("Service#Constructor"); } }
コンテナに登録して使ってみましょう。
var c = new UnityContainer(); // Serviceはシングルトン c.RegisterType<Service>(new ContainerControlledLifetimeManager()); // Personは任意のタイミングで破棄 c.RegisterType<Person>(new TransactionLifetimeManager()); // とりあえずインスタンス取得 Console.WriteLine("Resolve<Person>()"); var p = c.Resolve<Person>(); // 2回目取得しても同じインスタンスが取れることを確認 Console.WriteLine("Resolve<Person>()"); Console.WriteLine(p == c.Resolve<Person>() ? "同じインスタンス" : "違うインスタンス"); // インスタンスを明示的に破棄 Console.WriteLine("RaiseCompleted"); p.Complete(); // 違うインスタンスが取れることを確認 Console.WriteLine("Resolve<Person>()"); Console.WriteLine(p == c.Resolve<Person>() ? "同じインスタンス" : "違うインスタンス"); // コンテナの破棄時にDisposeが呼ばれることも確認 Console.WriteLine("Container#Dispose"); c.Dispose();
実行すると以下のような出力が得られます。
Resolve<Person>() Service#Constructor Person#Constructor Resolve<Person>() 同じインスタンス RaiseCompleted Person#Dispose Resolve<Person>() Person#Constructor 違うインスタンス Container#Dispose Person#Dispose
ちゃんとService
はシングルトンでPerson
がCompleted
イベントの発火とともに削除されてることが確認できます。
注意点
スレッドセーフではないのでWebアプリケーションみたいに不特定多数のスレッドからコンテナにアクセスするような環境下では使わないほうが幸せです。クライアントアプリとかでUIスレッド上からしかコンテナにアクセスしないような環境下で使いましょう。
まとめ
Unityはいいぞ。
LifetimeManager
の拡張は、はじめてやったけど結構簡単でした。
追記
3年前にやってた。