Quantcast
Channel: かずきのBlog@hatena
Viewing all articles
Browse latest Browse all 1387

DIコンテナのUnityのLifetimeManagerを拡張して任意のタイミングでインスタンスの破棄をする

$
0
0

UnityのLifetimeManagerはシングルトンで管理するContainerControlledLifetimeManagerか、デフォルトの毎回newする‘PerResolveLifetimeManager‘か、スレッド単位のPerThreadLifetimeManagerが用意されています。あとマニアックなところだと、ExternallyControlledLifetimeManagerとかいう弱参照で管理されるものもあります。

今回は、LifetimeManagerを拡張して任意のタイミングで破棄できるLifetimeManagerを作ってみようと思います。

破棄を通知するインターフェースの定義

まず、オブジェクトが破棄されたことを通知するインターフェースを定義します。とりあえず今回はイベントの発火でライフサイクルの終了を通知するようにしてみようと思うので、シンプルにCompletedイベントだけを持ったインターフェースを定義しました。

interface ITransactionPolicy
{
    event EventHandler Completed;
}

ITransactionPolicyを使ったLifetimeManagerの定義

後は、ITransactionPolicyCompletedイベントが起きたらインスタンスを破棄するようにする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はシングルトンでPersonCompletedイベントの発火とともに削除されてることが確認できます。

注意点

スレッドセーフではないのでWebアプリケーションみたいに不特定多数のスレッドからコンテナにアクセスするような環境下では使わないほうが幸せです。クライアントアプリとかでUIスレッド上からしかコンテナにアクセスしないような環境下で使いましょう。

まとめ

Unityはいいぞ。

LifetimeManagerの拡張は、はじめてやったけど結構簡単でした。

追記

3年前にやってた。

code.msdn.microsoft.com


Viewing all articles
Browse latest Browse all 1387

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>