読者です 読者をやめる 読者になる 読者になる
ようこそ。睡眠不足なプログラマのチラ裏です。

依存性をクラスごとに構成ファイルに定義。はい、DIです。

プログラミング C#3.0 VB.NET LINQ

マイクロソフトから提供されてるEntLib*1に含まれている DIコンテナを弄ってみた。
思っていたよりもいい感じにDIできますね。うんこれは便利。
Microsoft Enterprise Library 4.1 – October 2008


構成ファイル(*.config)から手軽に依存性の注入をしよう

DIコンテナを利用するのであれば、なるべく手軽に依存性を注入をしたい。
というわけで、お試し的に以下のようにDIできるようにしてみた。


ジェネリクスで型を指定をして、その型の依存性注入済みインスタンスを取得できるような感じです。
(もちろん、各DIコンテナの定義は構成ファイル(*.config)に定義してある前提で。)
いつものように例はC#で書きますが、VB.NETでも同様のことができます。
ちなみに、SampleInfomation()メソッドはDIとは直接関係ありませんので、無視してください。

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Reflection;
using DISample.Shinobi;
using Microsoft.Practices.Unity;

namespace DISample
{
    public class Program
    {
        private static void Main()
        {
            // 依存性を注入済みのインスタンスを取得
            var naruto = Utility.GetDependencyInjectedInstance<Naruto>();
            var kakashi = Utility.GetDependencyInjectedInstance<Kakashi>();
            var sasuke = Utility.GetDependencyInjectedInstance<Sasuke>();

            //うずまきナルトの忍術(注入された)
            naruto.Execute();
            //はたけカカシの忍術
            kakashi.Execute();
            //うちはサスケの忍術
            sasuke.Execute();

            Console.WriteLine();

            //ちょっとしたサンプル情報をば
            SampleInfomation();

            Console.ReadLine();
        }

        /// <summary>
        /// ちょっとしたサンプル情報
        /// </summary>
        private static void SampleInfomation()
        {

            var asm = Assembly.GetExecutingAssembly();
            Console.WriteLine("アセンブリ名:{0}", asm.FullName);

            var types = from t in asm.GetTypes()
                        where t.IsInterface
                        select t;

            Console.WriteLine();
            foreach (var item in from t in asm.GetTypes()
                                 from itf in types
                                 where !t.IsInterface
                                 where t.GetInterface(itf.FullName) != null
                                 orderby itf.Name, t.Name
                                 select new { itf, t })
            {
                Console.WriteLine("\t{0}インターフェイスを実装している型:{1}", item.itf.Name,item.t.Name);
            }

            Console.WriteLine();
            foreach (Type t in from t in asm.GetTypes()
                               from itf in types
                               from p in t.GetProperties()
                               from atr in p.GetCustomAttributes(typeof(DependencyAttribute), false)
                               where !t.IsInterface
                               where atr != null
                               where p.PropertyType == itf
                               orderby t.Name
                               select t)
            {
                Console.WriteLine("\tDI可能な型:{0}", t.FullName);
            }
        }
    }
}


お手軽感が伝わったでしょうか。
Utility.GetDependencyInjectedInstance()メソッドを呼び出しているだけでDIしています。
上記を実現するには、まず依存注入可能であることを表すIDependencyInjectableインターフェイスを
定義する必要があります。以下のような感じで作ってみました。

using System.Reflection;
using System.Linq;
using Microsoft.Practices.Unity;

namespace DISample
{
    /// <summary>
    /// 依存注入可能であることを表すインターフェイス
    /// </summary>
    public interface IDependencyInjectable<T> where T : new()
    {
        /// <summary>
        /// 構成ファイルのDirectoryを取得または設定します。
        /// (アセンブリのパスに対するDirectory)
        /// </summary>
        string ConfigurationDirectory { get; }

        /// <summary>
        /// コンテナ名(構成ファイル名)を取得または設定します。
        /// 通常はクラス名を指定します。
        /// </summary>
        string ContainerName { get; }
    }

    /// <summary>
    /// IDependencyInjectableExtentions
    /// </summary>
    public static class IDependencyInjectableExtentions{

        /// <summary>
        /// 依存注入可能なプロパティを保持しているか否かを取得します。
        /// </summary>
        /// <typeparam name="T">
        /// 対象の型
        /// 制約:new()
        /// </typeparam>
        /// <param name="self">自身</param>
        /// <returns>
        /// true :保持している
        /// false:保持していない
        /// </returns>
        public static bool IsDependencyInjectable<T>(this IDependencyInjectable<T> self) where T: new()
        {
            //依存注入可能なPropertyInfoを取得
            var pi = from p in self.GetType().GetProperties()
                         from atr in p.GetCustomAttributes(typeof(DependencyAttribute), false)
                         where atr != null
                         where p.PropertyType.IsInterface 
                         select p;
           
            return (pi.Count() > 0);
        }
    }
}

上記、拡張メソッドIsDependencyInjectable()は、
DependencyAttribute属性のプロパティの存在チェックです。
では、続きましてUtility.GetDependencyInjectedInstance()メソッドを見てみましょう。

using System;
using System.Configuration;
using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.Configuration;

namespace DISample
{
    public static class Utility
    {
        /// <summary>
        /// 依存性注入済みインスタンスを取得します。
        /// </summary>
        /// <typeparam name="T">対象の型</typeparam>
        /// <returns>依存性注入済みのTのインスタンス</returns>
        public static T GetDependencyInjectedInstance<T>() where T : IDependencyInjectable<T>, new()
        {
            T instance = Activator.CreateInstance<T>();

            // DI対象が依存注入可能なプロパティを持っていない場合
            if (!instance.IsDependencyInjectable<T>())
                throw new InvalidOperationException("コンテナ対象(" + instance.ContainerName + "型)は依存注入可能なプロパティを持っていません。"); 

            // DIコンテナを生成   
            var uc = new UnityContainer();

            // unityセクション取得
            var section = GetUnityConfigurationSection<T>(instance);

            // 構成ファイルの内容を適用
            section.Containers[instance.ContainerName].Configure(uc);

            // 依存性を注入   
            return (T)uc.BuildUp<T>(instance);
        }

        /// <summary>
        /// 設定ファイルから依存注入情報(UnityConfigurationSection)を取得します。
        /// </summary>
        /// <typeparam name="T">対象の型</typeparam>
        /// <param name="idi"></param>
        /// <returns></returns>
        private static UnityConfigurationSection GetUnityConfigurationSection<T>(IDependencyInjectable<T> idi) where T : IDependencyInjectable<T>, new()
        {
            //DI対象専用の構成ファイルより、unityセクションを読込
            var map = new ExeConfigurationFileMap();
            map.ExeConfigFilename = idi.ConfigurationDirectory + @"\" + idi.ContainerName + ".config";
            var config = ConfigurationManager.OpenMappedExeConfiguration(map, ConfigurationUserLevel.None);
            return (UnityConfigurationSection)config.GetSection("unity");
        }
    }
}


対象の*.config ファイルに対して構成ファイルの対応付けを定義する
ExeConfigurationFileMapクラスの利用がみそでしょうか。
もちろん、ConfigurationDirectoryの階層構造はnamespaceの階層構造に沿う感じで。


IDependencyInjectableインターフェイスを実装
依存性が注入されるやつら。

using System;
using Microsoft.Practices.Unity;

namespace DISample.Shinobi
{
    public class Naruto : IDependencyInjectable<Naruto>
    {
        [Dependency]
        public INinjyutsu Ninjyutsu { get; set; }

        public void Execute()
        {
            Console.WriteLine(this.Ninjyutsu.Excute());
        }

        #region IDependencyInjectable<Naruto> メンバ
        string IDependencyInjectable<Naruto>.ConfigurationDirectory
        {
            get { return "DependencyInjection"; }
        }

        string IDependencyInjectable<Naruto>.ContainerName
        {
            get { return this.GetType().Name; }
        }
        #endregion
    }

    public class Kakashi : IDependencyInjectable<Kakashi>
    {
        [Dependency]
        public INinjyutsu Ninjyutsu { get; set; }

        public void Execute()
        {
            Console.WriteLine(this.Ninjyutsu.Excute());
        }

        #region IDependencyInjectable<Kakashi> メンバ
        string IDependencyInjectable<Kakashi>.ConfigurationDirectory
        {
            get { return "DependencyInjection"; }
        }

        string IDependencyInjectable<Kakashi>.ContainerName
        {
            get { return this.GetType().Name; }
        }
        #endregion
    }

    public class Sasuke : IDependencyInjectable<Sasuke>
    {
        [Dependency]
        public INinjyutsu Ninjyutsu { get; set; }

        public void Execute()
        {
            Console.WriteLine(this.Ninjyutsu.Excute());
        }

        #region IDependencyInjectable<Naruto> メンバ
        string IDependencyInjectable<Sasuke>.ConfigurationDirectory
        {
            get { return "DependencyInjection"; }
        }

        string IDependencyInjectable<Sasuke>.ContainerName
        {
            get { return this.GetType().Name; }
        }
        #endregion
    }
}

DependencyAttribute属性が指定されているプロパティに依存性が注入されます。


注入される依存性
注入される依存性の定義です。

namespace DISample
{
    public interface INinjyutsu
    {
        string Excute();
    }

    public class Kagebunshin : INinjyutsu
    {
        public string Excute()
        {
            return "影分身の術";
        }
    }

    public class Rasengan : INinjyutsu
    {
        public string Excute()
        {
            return "螺旋丸";
        }
    }

    public class Raikiri : INinjyutsu
    {
        public string Excute()
        {
            return "雷切";
        }
    }

    public class Sharingan : INinjyutsu
    {
        public string Excute()
        {
            return "写輪眼";
        }
    }

    public class KatonGokakyu : INinjyutsu
    {
        public string Excute()
        {
            return "火遁豪火球の術";
        }
    }

    public class Chidori : INinjyutsu
    {
        public string Excute()
        {
            return "千鳥";
        }
    }
}

文字列を返しているだけです^^;


構成ファイルに依存性を定義

最後に依存性を定義してある構成ファイルの内容および、
実行結果を示します。


うずまきナルトの定義

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Microsoft.Practices.Unity.Configuration, Version=1.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
  </configSections>
  <unity>
    <!--型の別名を定義-->
    <typeAliases>
      <typeAlias alias="INinjyutsu" type="DISample.INinjyutsu, ConsoleApplication1"/>
      <typeAlias alias="kag" type="DISample.Kagebunshin, ConsoleApplication1"/>
      <typeAlias alias="rsn" type="DISample.Rasengan, ConsoleApplication1"/>
    </typeAliases>
    <containers>
      <container name="Naruto">
        <types>
          <type type="INinjyutsu" mapTo="kag"/>
        </types>
      </container>
    </containers>
  </unity>
</configuration>


はたけカカシの定義

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Microsoft.Practices.Unity.Configuration, Version=1.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
  </configSections>
  <unity>
    <!--型の別名を定義-->
    <typeAliases>
      <typeAlias alias="INinjyutsu" type="DISample.INinjyutsu, ConsoleApplication1"/>
      <typeAlias alias="srg" type="DISample.Sharingan, ConsoleApplication1"/>
      <typeAlias alias="rkr" type="DISample.Raikiri, ConsoleApplication1"/>
      <typeAlias alias="ktn" type="DISample.KatonGokakyu, ConsoleApplication1"/>
    </typeAliases>
    <containers>
      <container name="Kakashi">
        <types>
          <type type="INinjyutsu" mapTo="rkr"/>
        </types>
      </container>
    </containers>
  </unity>
</configuration>


うちはサスケの定義

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Microsoft.Practices.Unity.Configuration, Version=1.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
  </configSections>
  <unity>
    <!--型の別名を定義-->
    <typeAliases>
      <typeAlias alias="INinjyutsu" type="DISample.INinjyutsu, ConsoleApplication1"/>
      <typeAlias alias="tdr" type="DISample.Chidori, ConsoleApplication1"/>
      <typeAlias alias="ktn" type="DISample.KatonGokakyu, ConsoleApplication1"/>
    </typeAliases>
    <containers>
      <container name="Sasuke">
        <types>
          <type type="INinjyutsu" mapTo="tdr"/>
        </types>
      </container>
    </containers>
  </unity>
</configuration>


実行結果

影分身の術
雷切
千鳥

アセンブリ名:ConsoleApplication1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null

    IDependencyInjectable`1インターフェイスを実装している型:Kakashi
    IDependencyInjectable`1インターフェイスを実装している型:Naruto
    IDependencyInjectable`1インターフェイスを実装している型:Sasuke
    INinjyutsuインターフェイスを実装している型:Chidori
    INinjyutsuインターフェイスを実装している型:Kagebunshin
    INinjyutsuインターフェイスを実装している型:KatonGokakyu
    INinjyutsuインターフェイスを実装している型:Raikiri
    INinjyutsuインターフェイスを実装している型:Rasengan
    INinjyutsuインターフェイスを実装している型:Sharingan

    DI可能な型:DISample.Shinobi.Kakashi
    DI可能な型:DISample.Shinobi.Naruto
    DI可能な型:DISample.Shinobi.Sasuke

んー、結構いい感じです。
構成ファイルに定義されている依存性が、正しく注入されていますね(^ω^)