Dependency Injection 用のライブラリとして NInject を個人的に使っているのですが会社の仕事では オープンソースプロダクトを使用できなかったりする(ただし、Ajax Control Toolkit 等、 MSのは使えるという) ので、Dependency Injection のライブラリとして Unitiy を最近勉強しています。

今回は覚書として Unity でシングルトンを実現する方法を記載します。設定ファイルでシングルトンの設定も行えますが、今回はAPIを使用しています。

検証環境

  • Unity 2.0
  • APIを使用して シングルトンを設定

Unity は Enterprise Library 5 に含まれるものと同じものを使用していますが、NuGet で 単独でセットアップしました。

1. Unity のインストール

Unitiy は Enterprise Library に含まれています。現時点で、下記URLから Enterprise Library 5.0 のヘルプとインストールファイルを取得できます。付加情報として役に立ちそうなcodeplexとMSDNのリンクも記載します。

Microsoft Enterprise Library 5.0
http://www.microsoft.com/en-us/download/details.aspx?id=15104

patterns & practices – Enterprise Library
http://entlib.codeplex.com/

付録 A - Unity での依存関係注入
http://msdn.microsoft.com/ja-jp/library/hh319722(PandP.50).aspx

上記URLからセットアップファイルをダウンロードしてインストールできますが、今回は Unity 単体をインストールします。

Visual Studio で コンソールプロジェクトを作成します。 NuGet Package Manager を起動して,下図のように Online で Unity を検索します。 Unity を選択して、 Install ボタンをクリックしてインストールを行います。

Install が成功すると、プロジェクトに 下図の 3つの参照(Microsoft.Practices.ServiceLocation, Microsoft.Practices.Unity, Microsoft.Practices.Unity.Configuration )が表示されます。ServiceLocation は Unity と依存関係があるため同時にインストールされた dll です。

2. Unity の API を使用した シングルトンのサンプル

定義ファイルではなく、コンテナAPI を使用して シングルトンを実現するサンプルを掲載します。

2.1 サンプルクラスの作成

プロジェクトにファイル Singleton.cs を追加して、次のように作成します。本クラスは、後のテストコードで実際に シングルトンとなるクラスです。Sigleton クラスはコンストラクタで プロパティに時間を設定して、シングルトンであることの確認に使用しています。ISomeService インタフェースと SomeService クラスは シングルトンとして使用する ISomeService のマッピングとして SomeService を指定するサンプル用のクラスです。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace UnityTest
{
    class Singleton
    {
        public DateTime CreatedDateTime
        {
            get;
            set;
        }
        public Singleton()
        {
            this.CreatedDateTime = DateTime.Now;
        }

        public string GetCreateDateTime()
        {
            return CreatedDateTime.ToString("yyyy/MM/dd HH:mm:ss");
        }
    }

    public interface ISomeService
    {
        void doService();
    }

    public class SomeService : ISomeService
    {
        public void doService()
        {
            // do nothing
        }
    }
}

上記クラスを次のテストコードでシングルトンとして設定、取得してみます。

2.2 テストコードの作成

テストコードは次のようになります。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Practices.Unity;

namespace UnityTest
{
    class Program
    {
        static void Main(string[] args)
        {
            LifetimeManagerTest();
            Console.ReadLine();
        }


        static void LifetimeManagerTest()
        {
            // シングルトンの登録
            IUnityContainer unityContainer = new UnityContainer().RegisterType<Singleton>(new ContainerControlledLifetimeManager());
            
            var a = unityContainer.Resolve<Singleton>();
            Console.WriteLine(a.GetCreateDateTime());
            System.Threading.Thread.Sleep(4000);
            var b = unityContainer.Resolve<Singleton>();
            Console.WriteLine(b.GetCreateDateTime());

            // 名前付きでクラスのマッピングを登録すれば、別のシングルトンとして解決される。
            unityContainer.RegisterType<Singleton>("single2", new ContainerControlledLifetimeManager());
            var c = unityContainer.Resolve<Singleton>("single2");
            Console.WriteLine(c.GetCreateDateTime());
            System.Threading.Thread.Sleep(4000);
            var d = unityContainer.Resolve<Singleton>("single2");
            Console.WriteLine(d.GetCreateDateTime());

            // LifeTimeManagerはそのほか、
            // TransientLifetimeManager
            // PerThreadLifetimeManager
            // PerResolveLifetimeManager
            // ExternallyControlledLifetimeManager
            // などがある

            System.Threading.Thread.Sleep(4000);
            // RegisterInstance を使用して シングルトン設定
            Singleton t = new Singleton();
            unityContainer.RegisterInstance<Singleton>(t);
            var e = unityContainer.Resolve<Singleton>();
            Console.WriteLine(e.GetCreateDateTime());
            System.Threading.Thread.Sleep(4000);
            var f = unityContainer.Resolve<Singleton>();
            Console.WriteLine(f.GetCreateDateTime());

            // インタフェースを指定して シングルトンを登録
            SomeService service = new SomeService();
            unityContainer.RegisterInstance<ISomeService>(service);
        }
    }
}

動作させると、 UnityContainer.RegisterTypeメソッド でContainerControlledLifetimeManagerを指定することで Resolve メソッドで返されるオブジェクトがシングルトンになることがわかります。また、名前を指定した場合、別のインスタンスでシングルトンが実現されることが確認できます。そのほか、 UnityContainer.RegisterInstance<T>  を使用してマッピング先に インスタンスを直接登録(RegisterInstance の場合既定のLifetimeManagerは ContainerControlledLifetimeManager になる) してシングルトンを実現したり、できます。

詳細は Enterprise Library のヘルプをダウンロードしてヘルプの Unity Dependency Injection and Interception -> Configururing Unity -> Run-Time Configuration  の各説明を参照してください。

3. まとめ

説明は以上です。Unityでのシングルトンの実現方法は通常のシングルトンパターンと比べて柔軟です。 名前付けにより複数設定できたり、 LifeTimeManager を使用して コンテナのスコープの単位(いわゆる通常のシングルトン)、スレッド単位、Unityコンテナ外で制御など柔軟に制御できます。