UnityのLifetimeManager
はシングルトンで管理するContainerControlledLifetimeManager
か、デフォルトの毎回newする‘PerResolveLifetimeManager‘か、スレッド単位のPerThreadLifetimeManager
が用意されています。あとマニアックなところだと、ExternallyControlledLifetimeManager
とかいう弱参照で管理されるものもあります。
今回は、LifetimeManager
を拡張して任意のタイミングで破棄できるLifetimeManager
を作ってみようと思います。
破棄を通知するインターフェースの定義
まず、オブジェクトが破棄されたことを通知するインターフェースを定義します。とりあえず今回はイベントの発火でライフサイクルの終了を通知するようにしてみようと思うので、シンプルにCompleted
イベントだけを持ったインターフェースを定義しました。
interface ITransactionPolicy { event EventHandler Completed; }
ITransactionPolicyを使ったLifetimeManagerの定義
後は、ITransactionPolicy
のCompleted
イベントが起きたらインスタンスを破棄するようにするLifetimeManager
の実装を作るだけです。
sealed class TransactionLifetimeManager : LifetimeManager, IDisposable { private object value; public override object GetValue() { return value; } public override void RemoveValue() { this.Dispose(); } public override void SetValue(object newValue) { value = newValue; var tx = value as ITransactionPolicy; if (tx != null) { tx.Completed += this.Tx_Completed; } } public void Dispose() { this.Dispose(true); GC.SuppressFinalize(this); } private void Dispose(bool disposable) { if (disposable) { (value as IDisposable)?.Dispose(); this.value = null; } } private void 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"); } public event EventHandler Completed; public void Complete() { this.Completed?.Invoke(this, EventArgs.Empty); } public void 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年前にやってた。