Enterprise Library に含まれる Unity を使用することで、インスタンスの作成や既存のインスタンスに対して 依存性の注入 (Dependency Injection) を行うことができます。今回は Unity の使い方のメモとして、Constructor Injection や Property Injection, Methoc call Injection を行う方法をサンプルプログラムとともに記載します。

Unity 2.0 の全体的なヘルプは 下記オンラインのURLやEnterprise Library の codeplex のページから参照されている ヘルプ(chm)ファイルを参照して下さい。

次の環境で動作確認を実施しています

  • Visual Studio 2010 Professional
  • Enterprise Library 5.0

1. Unity のセットアップ

Unity のみを使用する場合は、 [C#] Unityでシングルトン で紹介しているように Unity と 依存性のあるアセンブリのみを NuGet から取得できます。Enterprise Library をインストールする場合は、Microsoft Enterprise Library 5.0 からダウンロードできます。

最新版は codeplex の Enterprise Library の URL から確認してください。

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

2. 必要なdll をプロジェクトの参照に追加

NuGet 経由で Unity をセットアップしていない場合は、 プロジェクトにUnity の dll 参照を追加します。

必要なdll はMicrosoft.Practices.Unity.dll ですが、構成ファイルのUnity用の設定をプログラムで取得したり、Interceptionを使用する場合などは必要に応じてほかのdllをプロジェクトの参照に追加してください。どの場合に何のdllが必要かは下記UnityのヘルプのURLの先頭付近に記載されています。

Adding Unity to Your Application
http://msdn.microsoft.com/en-us/library/ff660927(PandP.20).aspx

以降では、それぞれの Injection を使用するサンプルプログラムを掲載しますが、サンプル内で使用されているクラスを先に記載しておきます。

    class SampleBase
    {
        public string WhichConstructorCalled { get; set; }
    }
    class Sample1 : SampleBase
    {
        /// <summary>
        /// コンストラクタが複数ある場合は、
        /// InjectionContainerAttribute
        /// でどのコンストラクタを使用するかを指定する
        /// </summary>
        [InjectionConstructor]
        public Sample1()
        {
            Console.WriteLine("引数なし");
            this.WhichConstructorCalled = "既定のコンストラクタ";
        }

        /// <summary>
        /// コンストラクタ
        /// </summary>
        /// <param name="name"></param>
        public Sample1(string name)
        {
            Console.WriteLine(name);
            this.WhichConstructorCalled = "nameを引数にとるコンストラクタ";
        }

        public Sample1(string name1, string name2)
        {
            Console.WriteLine(name1 + name2);
            this.WhichConstructorCalled = "nameを引数にとるコンストラクタ1";
        }
    }

    class Other1
    {
        public SampleBase Sample1 { get; set; }

        public SampleBase Sample2 { get; set; }
    }
    class Other2
    {
        [Dependency]
        public SampleBase Sample1 { get; set; }

        [OptionalDependency]
        public SampleBase Sample2 { get; set; }
    }

    class MethodInj
    {
        public MethodInj()
        {
            Console.WriteLine("コンストラクタ called");
        }
        [InjectionMethod]
        public void Initialize()
        {
            Console.WriteLine("Injection is called");
        }
        public void Initialize2()
        {
            Console.WriteLine("Injection2 is called");
        }
    }

DependencyAttribute や InjectionConstructorAttribute , InjectionMethodAttribute はUnityContainer に対して InjectionMember から派生した インスタンス を指定しない場合の既定の振る舞いに影響を当てます。詳細は下記ヘルプを参照

Annotating Objects for Constructor Injection
http://msdn.microsoft.com/en-us/library/ff660875(PandP.20).aspx

Annotating Objects for Property (Setter) Injection
http://msdn.microsoft.com/en-us/library/ff660903(PandP.20).aspx

Annotating Objects for Method Call Injection
http://msdn.microsoft.com/en-us/library/ff660873(PandP.20).aspx

3.Constructor Injection を使用してみる

インスタンス作成時にコンストラクタに対して インジェクション を行う、Constructor Injection を使用する サンプルプログラムを記載します。InjectionConstructor を指定して コンストラクタの設定値を指定したり、 ParameterOverride を Resolve メソッドに指定して、設定値を上書きして解決を行っています。また、 InjectionConstructor Attribute がクラスに付与されている場合、そのコンストラクタが解決時に使用されます。

        static void ConstructorInjectionTest()
        {
            IUnityContainer container = new UnityContainer();
            // コンストラクタが1つの場合そのコンストラクタを使用
            // 複数ある場合は InjectionConstructorAttribute をコンストラクタ
            // に付与することで使用するコンストラクタを指定できる
            container.RegisterType<SampleBase, Sample1>();
            SampleBase s = container.Resolve<SampleBase>();
            Console.WriteLine(s.WhichConstructorCalled);
            // InjectionConstructor を使用することで どのコンストラクタを使用するかを
            // 指定できる
            container.RegisterType<SampleBase, Sample1>(new InjectionConstructor("nameを引数にとるVersion"));
            s = container.Resolve<SampleBase>();
            Console.WriteLine(s.WhichConstructorCalled);
            // コンストラクタのInjectionはResolverOverride を指定することで
            // Injectionの設定を上書きできる
            s = container.Resolve<SampleBase>(new ParameterOverride("name", "hello"));
            Console.WriteLine(s.WhichConstructorCalled);
        }

対象のクラスが複数のコンストラクタを持つ場合、 RegisterType メソッドで InjectionConstructor を指定していない場合に、UnityContainer は Resolve メソッドが呼び出されたときに、どのコンストラクタが使用するでしょうか。 UnityContainer のコンストラクタの選択基準については、Annotating Objects for Constructor Injection の"How Unity Resolves Target Constructors and Parameters" に説明が記載されています。簡単に記載すると次の通りです。

  1. InjectionConstructor Attribute が付与されている場合、そのコンストラクタを使用する
  2. InjectionConstructorAttribute が指定されていない場合、最も引数の多いコンストラクタを使用する
  3. もっとも引数の多いコンストラクタが複数ある場合、例外を発生させる

4.Property Injection を使用してみる

解決時に プロパティに対して インジェクションを Property Injection を行うサンプルを記載します。InjectionProperty を RegisterType 時に指定しない場合は、 Dependency Attribute を付与したプロパティに対して Injection が行われます。 またサンプルでは、 PropertyOverride を Resolve メソッド呼び出し時に 指定して InjectionProperty の設定を上書きする方法も記載しています。

        static void PropertyInjectionTest()
        {
            IUnityContainer container = new UnityContainer();
            container.RegisterType<SampleBase, Sample1>();
            container.RegisterType<SampleBase, Sample1>("p", new InjectionConstructor(string.Empty));
            container.RegisterType<Other1>(new InjectionProperty("Sample2"));

            Other1 o1 = container.Resolve<Other1>();
            Console.WriteLine(o1.Sample2.WhichConstructorCalled);

            var t = container.Resolve<SampleBase>("p", new ParameterOverride("name", "hello"));
            o1 = container.Resolve<Other1>(new PropertyOverride("Sample2", t));
            Console.WriteLine(o1.Sample1 == null);
            Console.WriteLine(o1.Sample2.WhichConstructorCalled);

            // Dependency がある場合はそのプロパティが解決される
            Other2 o2 = container.Resolve<Other2>();
            Console.WriteLine(o2.Sample1.WhichConstructorCalled);

        }

DependencyAttribute は プロパティーだけでなく コンストラクタやメソッドの 引数に対して付与することができます。Unity Container の外部でインスタンス化されたオブジェクトに対して Property Injection を行う場合は、 BuildUp メソッドを使用してください。

5. Method Call Injection を使用してみる

Method Call Injection を設定すると Unity コンテナによって クラスがインスタンス化されたときに Method call Injection で指定したメソッドを呼び出されるようにすることができます。

RegisterInstance で インスタンスを登録していると Method call Injection で設定した メソッドは Resolve メソッド呼び出し時には呼び出されません。なぜなら初期済みだからです。

UnityContainer から作成していない オブジェクトに対して 依存性の注入 (Dependency Injection を行うために、 BuildUp メソッドを使用した場合、Unity コンテナに登録した型に対して Method call Injection が設定されていると 登録されている メソッドが呼び出されるので注意してください。

サンプルプログラムを記載します。

        /// <summary>
        /// Injectionが実施されるときに
        /// 呼び出される
        /// </summary>
        static void MethodInjectionTest()
        {
            IUnityContainer container = new UnityContainer();
            var v = container.Resolve<MethodInj>();

            container.RegisterType<MethodInj>(new InjectionMethod("Initialize2"));
            v = container.Resolve<MethodInj>();
            // BuildUp した時も Initialize2 が呼び出されるので注意
            MethodInj i = new MethodInj();
            container.BuildUp<MethodInj>(i);
        }

サンプルでは InjectionMethod を RegisterType 時に指定するか、 InjectionMethod Attribute を付与することで Method Call Injection が行われるようにしています。

6. まとめ

Unity を使用した Injection の説明は以上です。Unity を使用すると アプリケーション構成ファイルを使用して Injection の設定が行えます。詳細は ヘルプ を参照してください。また、 Unity の Interception を使用することで アスペクト指向 プログラミンを行えます。 詳細は Unity のヘルプや 下記 URL を参照してください。

アスペクト指向プログラミング、インターセプト、および Unity 2.0
http://msdn.microsoft.com/ja-jp/magazine/gg490353.aspx