SimpleInjectorというIoCフレームワーク(DIコンテナ)がすごくシンプルで使いやすいので、最近気に入っているのですが、基本的な使い方を忘れないうちにメモメモ。
SimpleInjectorの公式ページは以下です。
このライブラリ、オープンソースで開発されているのですが、ドキュメントがすごく充実しています。 また、 Framework Design Guidelinesをすごく意識してソースが書かれているので、ソースがすごく綺麗で勉強になります。その辺の内容は、ドキュメントの
Design Principles — Simple Injector 2 documentation
に書いてあります。
Framework Design Guidelinesは以下の事です。
書籍にもなっていて、amazonで買えますね。日本では、「.NETのクラスライブラリ設計」って名前になってます。この本、C#好きな人は結構持ってるんじゃないでしょうか。私も持っていまして、すごくお勧めです。

.NETのクラスライブラリ設計 (Microsoft.net Development Series)
- 作者: Krzysztof Cwalina,Bard Abrams,藤原雄介
- 出版社/メーカー: 日経BP社
- 発売日: 2009/12/23
- メディア: 単行本
- 購入: 10人 クリック: 603回
- この商品を含むブログ (35件) を見る
で、そんなSimpleInjectorなんですが、簡単に使えるんだけど機能はめちゃ豊富なので基本的な使い方の部分のみをメモしてます。(私も最近使い始めたので、あまり深く知りません。)
以下サンプル
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace TrySimpleInjector | |
{ | |
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Threading; | |
using System.Threading.Tasks; | |
using SimpleInjector; | |
internal class Program | |
{ | |
private static void Main() | |
{ | |
// | |
// SimpleInjectorは、名前の通りシンプルな構成のDIコンテナライブラリ。 | |
// 簡単に使えて、速度が速いのが特徴。シンプルなだけでなく、機能も豊富にある。 | |
// (簡単に利用することもできるし、細かい調整もできるという意味) | |
// | |
// また、オープンソースで開発されているライブラリであるがドキュメントが豊富に揃っている。 | |
// https://simpleinjector.readthedocs.org/en/latest/index.html | |
// | |
// 以下のプラットフォームをサポートしている。(2015年3月時点) | |
// ・.NET 4.0以上 | |
// ・Silverlight 4以上 | |
// ・Windows Phone 8 | |
// ・Windows Store Apps | |
// ・Mono | |
// Monoをサポートしているので、Xamarin Studioでも利用できる。 | |
// | |
// 公式URLは https://simpleinjector.org/index.html となっており | |
// ソースは、CodePlexで管理されている。 | |
// | |
// インストールは、公式ページからライブラリをダウンロードして利用するか | |
// NuGetから以下でインストールする。 | |
// | |
// $ Install-Package SimpleInjector | |
// | |
// | |
// 利用する場合、以下の名前空間を using に追加 | |
// | |
// using SimpleInjector; | |
// | |
// | |
// SimpleInjectorを利用する場合、まず最初にコンテナを作成する必要がある | |
// | |
var diContainer = new Container(); | |
// | |
// 次にコンテナに対して登録を行っていく。 | |
// SimpleInjectorは、設定ファイルを持たないタイプのDIコンテナ。 | |
// 必要な設定はコードから行う。 | |
// | |
// 最もシンプルな形の登録方法は Register メソッドを利用する。 | |
// Register<インターフェース, 具象クラス>(); | |
// とするのが、一番シンプルな形。 | |
// | |
// この時、引数にLifestyleを指定することもできる。 | |
// Lifestyle.Transient | |
// と指定すると、都度インスタンスを作成して返してくれる。 | |
// Lifestyle.Singleton | |
// と指定すると、シングルトンインスタンスを返してくれる。 | |
// 指定しない場合は、Lifestyle.Transientと同じことになる。 | |
// | |
diContainer.Register<IHasName, Data1>(); | |
// | |
// Initializerという初期化処理を追加することもできる。 | |
// 以下の例では、上でコンテナに登録した IHasName という | |
// インターフェースの初期化処理を追加している。 | |
// | |
// Initializerには、Action<T>を設定する。 | |
// 型引数Tには、Registerで指定した具象クラスのインスタンスが | |
// 引数として渡される。 | |
// | |
diContainer.RegisterInitializer<IHasName>(x => | |
{ | |
x.Name = "hello world " + x.GetType().Name; | |
}); | |
// | |
// Registerメソッドには、上述した インターフェースと具象クラスの | |
// 組み合わせを指定する以外にもオーバーロードが存在し | |
// 単純に型を指定することもできる。 | |
// | |
// この場合、引数に Func<T> を指定して 自前でインスタンス生成用の | |
// 処理を渡すこともできる。(instanceCreator) | |
// instanceCreatorを指定しない場合は、普通にnewされることとなる。 | |
// | |
// 以下は、普通にSimpleInjectorによってnewされる. | |
// diContainer.Register<Data2>(); | |
// | |
// 初期化処理 (instanceCreator) を付与して登録 | |
diContainer.Register<Data2>(() => | |
{ | |
return new Data2 | |
{ | |
Value = "hello world" | |
}; | |
}); | |
// | |
// コンテナにDIしてもらうために、全ての型を登録する必要はない。 | |
// 例として、本ソースコード内に定義されている Data3 というクラスは | |
// コンストラクタが以下のような宣言となっているが、コンテナにRegistしていない。 | |
// | |
// public Data3(IHasName a, Data2 b) | |
// | |
// このData3というクラスはコンテナに登録する必要はない。 | |
// コンテナに対して、「Data3のインスタンスを作成せよ」と | |
// 指示した際に、SimpleInjectorが必要なデータを作成して | |
// DIしてくれる。(後述するGetInstance<Data3>の部分を参照。) | |
// | |
// コンテナに登録するのは、依存性を断ち切って利用したいコンポーネントを登録する。 | |
// | |
// | |
// シングルトンインスタンスとしてコンテナに登録することもできる。 | |
// その場合 | |
// RegisterSingle メソッド | |
// を利用が利用できる。(RegisterメソッドにLifestyle.Singletonを指定しても同じ事になる) | |
// | |
// 上述しているように、コンテナにはオーバーロードが定義されているので | |
// RegisterSingle(Type, object); | |
// RegisterSingle<TService, TImpl>(); | |
// RegisterSingle<TConcrete>(); | |
// RegisterSingle<TService>(Func<TService> instanceCreator); | |
// などのオーバーロードから好きな方法を選んで登録する。 | |
// 他のDIコンテナと同様、シングルトンオブジェクトの管理は | |
// DIコンテナが担うため、利用する側は気にする必要はない。 | |
// | |
diContainer.RegisterSingle<Data4>(() => | |
{ | |
// | |
// シングルトンインスタンスをどのように構築するかを | |
// 指定したい場合は、このようにFuncを作って中で構築する。 | |
// 当然中で、コンテナからインスタンスを取得して、それを | |
// 利用することも可能。 | |
// | |
// コンテナはData1を作成してくれる | |
var a = diContainer.GetInstance<IHasName>(); | |
// コンテナData2を作成する際に初期化処理を行った上でインスタンスを返してくれる | |
var b = diContainer.GetInstance<Data2>(); | |
// コンテナに登録していないが、コンストラクタインジェクションを行って必要な | |
// オブジェクトを設定してインスタンスを作成してくれる。 (auto-wiring) | |
var c = diContainer.GetInstance<Data3>(); | |
return new Data4(a, b, c, "prepreprefix:"); | |
}); | |
// | |
// 当然、自前の型の登録だけでなく、すでに存在しているクラスでも登録できる。 | |
// | |
diContainer.Register<IList<string>>(() => new List<string>()); | |
diContainer.Register<IDictionary<string, object>>(() => new Dictionary<string, object>()); | |
// | |
// プロパティインジェクション (Property Injection) | |
// | |
// SimpleInjectorはデフォルトで対応しているのはコンスタラクタインジェクションのみ | |
// となっている。しかし、都合によりコンストラクタで依存性を注入できないパターンも存在する。 | |
// その場合、プロパティインジェクションを利用する。 | |
// | |
// SimpleInjectorにおいて、プロパティインジェクションは RegisterInitializer メソッドで行う。 | |
// | |
// 以下のData5クラスは、引数なしのコンストラクタしか持っておらずコンストラクタインジェクションが | |
// 行えないクラスで、CreationDateTimeプロパティを持っている。このプロパティに対してインスタンス生成時に | |
// 自動で値を指定したい場合、以下のようにする。 | |
// | |
// (*) 一つの型に対して、複数のInitializerを設定することは可能となっている。 | |
// 複数設定されている場合コンテナは順にInitializerを適用して、全てのInitializerが適用された後に | |
// インスタンスを返す。適用されるInitializerの順番はコンテナによって登録した順番となることが保証されている。 | |
// | |
diContainer.RegisterInitializer<Data5>(instance => | |
{ | |
instance.CreationDateTime = DateTime.Now; | |
} | |
); | |
// | |
// 一つのインターフェースに対して、複数の型を紐付け | |
// | |
// インターフェースと具象クラスが一対一ではなく、一対Nの場合 | |
// RegisterAllメソッドを利用する。 | |
// | |
// インスタンスをコンテナから取得する場合、コレクションで取得することになる。 | |
// | |
// 注意)コンテナでは、Registerで登録した内容とRegisterAllで登録した内容は | |
// 別管理されている。Register<IData, Data1>()とRegisterAll<IData>(typeof(Data1)) | |
// は別物であることに注意。 | |
// | |
diContainer.RegisterAll<IData>(typeof(Data1), typeof(Data2), typeof(Data3)); | |
// | |
// キーを識別子として利用してインスタンス取得 | |
// | |
// SimpleInjectorの機能としては存在しておらず、またドキュメント上でもおすすめされていないが | |
// 複数のインスタンスを同じインターフェースでまとめあげて、その中からキーを指定して一つだけ | |
// 取得したいという場合に以下のような方法が使える。 | |
// | |
// やり方としては、予めファクトリクラスを用意しておいて、コンテナに登録するのはファクトリクラスとしておく。 | |
// 後はコンテナからファクトリのインスタンスを取得して、実際のデータはファクトリにキーを渡して | |
// 取得するようにする。ファクトリインスタンスは、一つあればいいので、RegisterSingleでシングルトン管理してもらう。 | |
// | |
diContainer.RegisterSingle<IDataFactory>(() => | |
{ | |
return new DataFactory | |
{ | |
{"data1", () => diContainer.GetInstance<Data1>()}, | |
{"data2", () => diContainer.GetInstance<Data2>()}, | |
{"data3", () => diContainer.GetInstance<Data3>()} | |
}; | |
} | |
); | |
// | |
// 登録を終えたら、最後にVerifyメソッドを呼び出す。 | |
// 必須ではないが、このメソッドを呼び出す事により | |
// コンテナが自身の登録状態を検証して、おかしい場合はエラーに | |
// してくれる。 | |
// | |
diContainer.Verify(); | |
// | |
// 一旦登録を終えたあとは、GetInstance<T>とすることで | |
// いつでもインスタンスを取得することができる。 | |
// | |
// 必要なのは、コンテナだけなのでコンテナオブジェクトは | |
// アプリ内でグローバルにアクセスできる場所に保持しておく | |
// | |
// 補足として、一度Verifyした後に再度Registerしようとすると | |
// コンテナがエラーを発生させるので注意。 | |
// InvalidOperationException: | |
// The container can't be changed after the first call to GetInstance, GetAllInstances and Verify. | |
// | |
var obj = diContainer.GetInstance<IHasName>(); | |
Console.WriteLine(obj.GetType().Name); | |
var obj2 = diContainer.GetInstance<Data2>(); | |
Console.WriteLine(obj2.GetType().Name); | |
var obj3 = diContainer.GetInstance<Data3>(); | |
Console.WriteLine(obj3.Print()); | |
var obj4 = diContainer.GetInstance<Data4>(); | |
Console.WriteLine(obj4.Print()); | |
Console.WriteLine(diContainer.GetInstance<Data5>().CreationDateTime); | |
Thread.Sleep(TimeSpan.FromSeconds(1)); | |
Console.WriteLine(diContainer.GetInstance<Data5>().CreationDateTime); | |
var dataInstanceCollection = diContainer.GetAllInstances<IData>(); | |
foreach (var item in dataInstanceCollection) | |
{ | |
Console.WriteLine(item.GetType().Name); | |
} | |
var factory = diContainer.GetInstance<IDataFactory>(); | |
var idata = factory.Create("data3"); | |
Console.WriteLine(idata.GetType().Name); | |
Console.ReadLine(); | |
} | |
} | |
internal interface IData | |
{ | |
// this is marker-interface. | |
} | |
internal interface IHasName | |
{ | |
string Name { get; set; } | |
} | |
internal class Data1 : IHasName, IData | |
{ | |
public string Name { get; set; } | |
} | |
internal class Data2 : IData | |
{ | |
public string Value { get; set; } | |
} | |
internal class Data3 : IData | |
{ | |
private readonly IHasName _a; | |
private readonly Data2 _b; | |
public Data3(IHasName a, Data2 b) | |
{ | |
_a = a; | |
_b = b; | |
} | |
public string Print() | |
{ | |
return string.Format("{0} {1}", _a.Name, _b.Value); | |
} | |
} | |
internal class Data4 : IData | |
{ | |
private readonly IHasName _a; | |
private readonly Data2 _b; | |
private readonly Data3 _c; | |
private readonly string _prefix; | |
public Data4(IHasName a, Data2 b, Data3 c, string prefix) | |
{ | |
_a = a; | |
_b = b; | |
_c = c; | |
_prefix = prefix; | |
} | |
public string Print() | |
{ | |
_c.Print(); | |
return string.Format("{0}{1} {2} ", _prefix, _a.Name, _b.Value); | |
} | |
} | |
internal interface IHasCreationDateTime | |
{ | |
DateTime CreationDateTime { get; } | |
} | |
internal class Data5 : IHasCreationDateTime, IData | |
{ | |
public Data5() | |
{ | |
} | |
public DateTime CreationDateTime { get; internal set; } | |
} | |
internal interface IDataFactory | |
{ | |
IData Create(string name); | |
} | |
internal class DataFactory : Dictionary<string, Func<IData>>, IDataFactory | |
{ | |
public IData Create(string name) | |
{ | |
return this[name](); | |
} | |
} | |
} |
実行結果は以下のようになります。
Data1 Data2 hello world Data1 hello world prepreprefix:hello world Data1 hello world 2015/03/17 01:19:20 2015/03/17 01:19:21 Data1 Data2 Data3 Data3
また、勉強がすすんだら、追記を書こうと思います。
過去の記事については、以下のページからご参照下さい。
サンプルコードは、以下の場所で公開しています。