依存性注入フレームワーク構成の比較

多くの場合、どのIoCコンテナーが特定のプロジェクトに適しているかを自問しました。 彼らのパフォーマンスはコインの片側にすぎません。 完全なパフォーマンスの比較はここで見つけることができます 。 コインのもう一方の側面は、シンプルさと学習の速さです。 そこで、この観点からいくつかのコンテナを比較することにし、Autofac、Simple Injector、StructureMap、Ninject、Unity、Castle Windsorを使用しました。 私の意見では、これらは最も人気のあるIoCコンテナーです。 それらのいくつかは、NuGetパッケージのトップ20のリストで見つけることができます。また他のパッケージを好みに追加しました。 個人的には、私はAutofacが本当に好きで、この記事に取り組んでいる間、ほとんどの場合これが最良の選択であることをさらに確信しました。







構成やコンポーネントの登録など、IoCコンテナーの基本について説明します。 生涯スコープと高度なフィッチの管理も比較するという考えがあります。 コード例は、LifetimeScopesExamples GitHubリポジトリにあります







ドキュメント



この記事の作業中に、IoCの一部のドキュメントを参照する必要がありました。 残念ながら、すべてのIoCコンテナに良い説明があるわけではなく、Googleで解決策を探すことを余儀なくされました。 したがって、次の要約が得られた。







品質 解説
Autofac スーパー ドキュメントには、必要なものがすべて含まれています。 さらに、私は何もグーグルする必要はありませんでした。 例は明確で便利です。
シンプルなインジェクター よし ドキュメントは前のものと似ていますが、少し生々しく見えます。 いくつかのポイントをグーグルで検索する必要がありましたが、すぐに解決策が見つかりました。
構造マップ すべてのケースがドキュメントに記載されているわけではありません。 式、プロパティ、メソッドのインジェクションで登録するなどの記述は不適切です。 グーグルにする必要がありました。
Ninject あります すべてのケースが説明されているわけではありません。 式、プロパティ、メソッドのインジェクションで登録するなどの記述は不適切です。 グーグルにする必要がありました。 ソリューションを見つけるのは困難でした。
団結 悪い テキストの量にもかかわらず、ドキュメントは役に立たない テキストの「シート」を理解する必要があります。 すべてのケースはグーグルで検索する必要がありましたが、見つけるのは困難です。
キャッスルウィンザー すべてのケースが説明されているわけではなく、理解できない例もあります。 私はそれをググる必要がありました。


あなたが自分で見ることができるドキュメントリンク:









構成



ここでは、XMLによる構成は考慮しません。 すべての例では、インターフェースを介してIoCコンテナーを構成する一般的なケースについて説明します。 ここでは、次を見つけることができます。









この記事の目的は、各ケースの実例を提供することです。 パラメータ化された登録などの複雑なシナリオは、このテキストの範囲外です。







オブジェクトとテストケースモデル



IoCコンテナーをテストするために、単純なモデルを作成しました。 プロパティとメソッドのインジェクションを使用するには、いくつかの変更があります。 IoCコンテナの一部では、プロパティまたはメソッドを介して初期化するために特別な属性を使用する必要があります。 これについては各セクションで明示的に書きました。







/************* * Interfaces * **************/ public interface IAuthorRepository{ IList<Book> GetBooks(Author parent); } public interface IBookRepository{ IList<Book> FindByParent(int parentId); } public interface ILog{ void Write(string text); } /*********************************************** * Implementation for injection via constructor * ***********************************************/ internal class AuthorRepositoryCtro : IAuthorRepository{ private readonly IBookRepository _bookRepository; private readonly ILog _log; public AuthorRepositoryCtro(ILog log, IBookRepository bookRepository) { _log = log; _bookRepository = bookRepository; } public IList<Book> GetBooks(Author parent) { _log.Write("AuthorRepository:GetBooks()"); return _bookRepository.FindByParent(parent.Id); }} internal class BookRepositoryCtro : IBookRepository{ private readonly ILog _log; public BookRepositoryCtro(ILog log) { _log = log; } public IList<Book> FindByParent(int parentId) { _log.Write("BookRepository:FindByParent()"); return null; }} internal class ConsoleLog : ILog{ public void Write(string text) { Console.WriteLine("{0}", text); }}
      
      





テストシナリオでは、コンテナを作成し、そこからオブジェクトを2回取得して、タイムライフスコープ管理がどのように機能するかを確認します。 これは次の記事になります。







 private static void Main(string[] args){ var resolver = Configuration.Simple(); /*********************************************************** * both resolving use the same method of IBookRepository * * it depends on lifetime scope configuration whether ILog * * would be the same instance (the number in the output * * shows the number of the instance) * ***********************************************************/ // the 1st resolving var books = resolver.Resolve<IAuthorRepository>().GetBooks(new Author()); // the 2nd resolving resolver.Resolve<IBookRepository>().FindByParent(0); System.Console.WriteLine("Press any key..."); System.Console.ReadKey(); }
      
      





コンストラクターによる実装



この構成では、基本バージョンに特別な属性や名前は必要ありません。







Autofac



 var builder = new ContainerBuilder(); builder.RegisterType<AuthorRepositoryCtro>().As<IAuthorRepository>(); builder.RegisterType<BookRepositoryCtro>().As<IBookRepository>(); builder.RegisterType<ConsoleLog>().As<ILog>(); var container = builder.Build();
      
      





シンプルなインジェクター



 var container = new Container(); container.Register<IAuthorRepository, AuthorRepositoryCtro>(); container.Register<IBookRepository, BookRepositoryCtro>(); container.Register<ILog, ConsoleLog>();
      
      





構造マップ



 var container = new Container(); container.Configure(c => { c.For<IAuthorRepository>().Use<AuthorRepositoryCtro>(); c.For<IBookRepository>().Use<BookRepositoryCtro>(); c.For<ILog>().Use<ConsoleLog>(); });
      
      





Ninject



 var container = new StandardKernel(); container.Bind<IAuthorRepository>().To<AuthorRepositoryCtro>(); container.Bind<IBookRepository>().To<BookRepositoryCtro>(); container.Bind<ILog>().To<ConsoleLog>();
      
      





団結



 var container = new UnityContainer(); container.RegisterType<IAuthorRepository, AuthorRepositoryCtro>(); container.RegisterType<IBookRepository, BookRepositoryCtro>(); container.RegisterType<ILog, ConsoleLog>();
      
      





キャッスルウィンザー



 var container = new WindsorContainer(); container.Register(Component.For<IAuthorRepository>().ImplementedBy<AuthorRepositoryCtro>()); container.Register(Component.For<IBookRepository>().ImplementedBy<BookRepositoryCtro>()); container.Register(Component.For<ILog>().ImplementedBy<ConsoleLog>());
      
      





プロパティインジェクション



一部のIoCコンテナでは、初期化のプロパティを認識するのに役立つ特別な属性の使用が必要です。 オブジェクトモデルとIoCコンテナが強く結び付けられるため、私は個人的にこのアプローチが好きではありません。 Ninjectでは[Inject]属性を使用する必要があり、 Unityでは[Dependency]属性が必要です。 同時に、 Castle Windsorはプロパティを初期化するために何も必要としません。 それはデフォルトで彼に起こります。







Autofac



 var builder = new ContainerBuilder(); builder.RegisterType<AuthorRepositoryCtro>().As<IAuthorRepository>().PropertiesAutowired(); builder.RegisterType<BookRepositoryCtro>().As<IBookRepository>().PropertiesAutowired(); builder.RegisterType<ConsoleLog>().As<ILog>(); var container = builder.Build();
      
      





シンプルなインジェクター



       ,       .
      
      





構造図



 var container = new Container(); container.Configure(c => { c.For<IAuthorRepository>().Use<AuthorRepositoryProp>(); c.For<IBookRepository>().Use<BookRepositoryProp>(); c.For<ILog>().Use(() => new ConsoleLog()); c.Policies.SetAllProperties(x => { x.OfType<IAuthorRepository>(); x.OfType<IBookRepository>(); x.OfType<ILog>(); }); });
      
      





Ninject



 var container = new StandardKernel(); container.Bind<IAuthorRepository>().To<AuthorRepositoryProp>(); container.Bind<IBookRepository>().To<BookRepositoryProp>(); container.Bind<ILog>().To<ConsoleLog>();
      
      





団結



 var container = new UnityContainer(); container.RegisterType<IAuthorRepository, AuthorRepositoryProp>(); container.RegisterType<IBookRepository, BookRepositoryProp>(); container.RegisterType<ILog, ConsoleLog>();
      
      





キャッスルウィンザー



 var container = new WindsorContainer(); container.Register(Component.For<IAuthorRepository>().ImplementedBy<AuthorRepositoryProp>()); container.Register(Component.For<IBookRepository>().ImplementedBy<BookRepositoryProp>()); container.Register(Component.For<ILog>().ImplementedBy<ConsoleLog>());
      
      





メソッドの展開



このアプローチは、前のアプローチと同様に、循環参照に役立ちます。 一方、これは避けるべき別のポイントを導入します。 簡単に言うと、APIは、オブジェクトの完全な作成にこのような初期化が必要であることを示唆しません。 ここで、 時間的結合についてもう少し説明します。







ここでも、一部のIoCコンテナでは、同じ欠点を持つ特別な属性を使用する必要があります。 Ninjectには、メソッドの[Inject]属性が必要です。 Unityでは、 [InjectionMethod]属性を使用する必要があります。 そのような属性でマークされたすべてのメソッドは、コンテナがオブジェクトを作成するときに実行されます。







Autofac



 var builder = new ContainerBuilder(); builder.Register(c => { var rep = new AuthorRepositoryMtd(); rep.SetDependencies(c.Resolve<ILog>(), c.Resolve<IBookRepository>()); return rep; }).As<IAuthorRepository>(); builder.Register(c => { var rep = new BookRepositoryMtd(); rep.SetLog(c.Resolve<ILog>()); return rep; }).As<IBookRepository>(); builder.Register(c => new ConsoleLog()).As<ILog>(); var container = builder.Build();
      
      





シンプルなインジェクター



 var container = new Container(); container.Register<IAuthorRepository>(() => { var rep = new AuthorRepositoryMtd(); rep.SetDependencies(container.GetInstance<ILog>(), container.GetInstance<IBookRepository>()); return rep; }); container.Register<IBookRepository>(() => { var rep = new BookRepositoryMtd(); rep.SetLog(container.GetInstance<ILog>()); return rep; }); container.Register<ILog>(() => new ConsoleLog());
      
      





構造マップ



 var container = new Container(); container.Configure(c => { c.For<IAuthorRepository>().Use<AuthorRepositoryMtd>() .OnCreation((c, o) => o.SetDependencies(c.GetInstance<ILog>(), c.GetInstance<IBookRepository>())); c.For<IBookRepository>().Use<BookRepositoryMtd>() .OnCreation((c, o) => o.SetLog(c.GetInstance<ILog>())); c.For<ILog>().Use<ConsoleLog>(); });
      
      





Ninject



 var container = new StandardKernel(); container.Bind<IAuthorRepository>().To<AuthorRepositoryMtd>() .OnActivation((c, o) => o.SetDependencies(c.Kernel.Get<ILog>(), c.Kernel.Get<IBookRepository>())); container.Bind<IBookRepository>().To<BookRepositoryMtd>() .OnActivation((c, o) => o.SetLog(c.Kernel.Get<ILog>())); container.Bind<ILog>().To<ConsoleLog>();
      
      





団結



 var container = new UnityContainer(); container.RegisterType<IAuthorRepository, AuthorRepositoryMtd>(); container.RegisterType<IBookRepository, BookRepositoryMtd>(); container.RegisterType<ILog, ConsoleLog>();
      
      





キャッスルウィンザー



 var container = new WindsorContainer(); container.Register(Component.For<IAuthorRepository>().ImplementedBy<AuthorRepositoryMtd>() .OnCreate((c, o) => ((AuthorRepositoryMtd) o).SetDependencies(c.Resolve<ILog>(), c.Resolve<IBookRepository>()))); container.Register(Component.For<IBookRepository>().ImplementedBy<BookRepositoryMtd>() .OnCreate((c, o) => ((BookRepositoryMtd)o).SetLog(c.Resolve<ILog>()))); container.Register(Component.For<ILog>().ImplementedBy<ConsoleLog>());
      
      





式の登録



前のセクションのほとんどのケースは、ラムダ式またはデリゲートに登録すること以外の何ものでもありません。 この登録方法は、オブジェクトの作成時にロジックを追加するのに役立ちますが、これは動的なアプローチではありません。 ダイナミクスについては、パラメータ化された登録を使用して、実行時に1つのコンポーネントの異なる実装を作成できるようにする必要があります。







Autofac



 var builder = new ContainerBuilder(); builder.Register(c => new AuthorRepositoryCtro(c.Resolve<ILog>(), c.Resolve<IBookRepository>())) .As<IAuthorRepository>(); builder.Register(c => new BookRepositoryCtro(c.Resolve<ILog>())) .As<IBookRepository>(); builder.Register(c => new ConsoleLog()).As<ILog>(); var container = builder.Build();
      
      





シンプルなインジェクター



 var container = new Container(); container.Register<IAuthorRepository>(() => new AuthorRepositoryCtro(container.GetInstance<ILog>(), container.GetInstance<IBookRepository>())); container.Register<IBookRepository>(() => new BookRepositoryCtro(container.GetInstance<ILog>())); container.Register<ILog>(() => new ConsoleLog());
      
      





構造マップ



 var container = new Container(); container.Configure(r => { r.For<IAuthorRepository>() .Use(c => new AuthorRepositoryCtro(c.GetInstance<ILog>(), c.GetInstance<IBookRepository>())); r.For<IBookRepository>() .Use(c => new BookRepositoryCtro(c.GetInstance<ILog>())); r.For<ILog>().Use(() => new ConsoleLog()); });
      
      





Ninject



 var container = new StandardKernel(); container.Bind<IAuthorRepository>().ToConstructor(c => new AuthorRepositoryCtro(c.Inject<ILog>(), c.Inject<IBookRepository>())); container.Bind<IBookRepository>().ToConstructor(c => new BookRepositoryCtro(c.Inject<ILog>())); container.Bind<ILog>().ToConstructor(c => new ConsoleLog());
      
      





または







 container.Bind<IAuthorRepository>().ToMethod(c => new AuthorRepositoryCtro(c.Kernel.Get<ILog>(), c.Kernel.Get<IBookRepository>())); container.Bind<IBookRepository>().ToMethod(c => new BookRepositoryCtro(c.Kernel.Get<ILog>())); container.Bind<ILog>().ToMethod(c => new ConsoleLog());
      
      





団結



 var container = new UnityContainer(); container.RegisterType<IAuthorRepository>(new InjectionFactory(c => new AuthorRepositoryCtro(c.Resolve<ILog>(), c.Resolve<IBookRepository>()))); container.RegisterType<IBookRepository>(new InjectionFactory(c => new BookRepositoryCtro(c.Resolve<ILog>()))); container.RegisterType<ILog>(new InjectionFactory(c => new ConsoleLog()));
      
      





キャッスルウィンザー



 var container = new WindsorContainer(); container.Register(Component.For<IAuthorRepository>() .UsingFactoryMethod(c => new AuthorRepositoryCtro(c.Resolve<ILog>(), c.Resolve<IBookRepository>()))); container.Register(Component.For<IBookRepository>() .UsingFactoryMethod(c => new BookRepositoryCtro(c.Resolve<ILog>()))); container.Register(Component.For<ILog>().UsingFactoryMethod(c => new ConsoleLog()));
      
      





Ninjectには、 ToMethodToConstructorを使用した設定の違いがあります。 簡単に言うと、ToContructorを使用する場合、条件も使用できます。 次の構成は、 ToMethodでは機能しません。







 Bind<IFoo>().To<Foo1>().WhenInjectedInto<Service1>(); Bind<IFoo>().To<Foo2>().WhenInjectedInto<Service2>();
      
      





契約による登録



場合によっては、構成コードをまったく作成する必要はありません。 一般的なシナリオは次のとおりです。必要な型を見つけるためにアセンブリをスキャンし、それらのインターフェイスを取得して、インターフェイス実装ペアとしてコンテナに登録します。 これは非常に大規模なプロジェクトには役立ちますが、開発者がプロ​​ジェクトに不慣れであることは困難です。 覚えておくべきいくつかのポイント。







Autofacは 、可能なすべての実装を登録し、内部配列に保存します。 ドキュメントによると、彼はデフォルトの解像度に最新のオプションを使用します。 Simple Injectorには、自動登録のための既製のメソッドはありません。 これは手動で行う必要があります(以下の例)。 StructureMapおよびUnityは、パブリック実装クラスを必要とします。 彼らのスキャナーは他の人を見ません。 Ninjectには、オプションのNuGetパッケージNinject.Extensions.Conventionsが必要です。 また、パブリック実装クラスも必要です。







Autofac



 var builder = new ContainerBuilder(); builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly()).AsImplementedInterfaces(); var container = builder.Build();
      
      





シンプルなインジェクター



 var container = new Container(); var repositoryAssembly = Assembly.GetExecutingAssembly(); var implementationTypes = from type in repositoryAssembly.GetTypes() where type.FullName.Contains("Repositories.Constructors") || type.GetInterfaces().Contains(typeof (ILog)) select type; var registrations = from type in implementationTypes select new { Service = type.GetInterfaces().Single(), Implementation = type }; foreach (var reg in registrations) container.Register(reg.Service, reg.Implementation);
      
      





構造マップ



 var container = new Container(); container.Configure(c => c.Scan(x => { x.TheCallingAssembly(); x.RegisterConcreteTypesAgainstTheFirstInterface(); }));
      
      





Ninject



 var container = new StandardKernel(); container.Bind(x => x.FromThisAssembly().SelectAllClasses().BindDefaultInterfaces());
      
      





団結



 var container = new UnityContainer(); container.RegisterTypes( AllClasses.FromAssemblies(Assembly.GetExecutingAssembly()), WithMappings.FromAllInterfaces);
      
      





キャッスルウィンザー



 var container = new WindsorContainer(); container.Register(Classes.FromAssembly(Assembly.GetExecutingAssembly()) .IncludeNonPublicTypes() .Pick() .WithService.DefaultInterfaces());
      
      





モジュールを使用した登録



モジュールは、構成を共有するのに役立ちます。 コンテキスト(データアクセス、ビジネスオブジェクト)または目的(運用、テスト)ごとにグループ化できます。 IoCの一部のコンテナは、モジュールを検索してアセンブリをスキャンできます。 ここでは、それらを使用する主な方法について説明しました。







Autofac



 public class ImplementationModule : Module { protected override void Load(ContainerBuilder builder) { builder.RegisterType<AuthorRepositoryCtro>().As<IAuthorRepository>(); builder.RegisterType<BookRepositoryCtro>().As<IBookRepository>(); builder.RegisterType<ConsoleLog>().As<ILog>(); } } /********* * usage * *********/ var builder = new ContainerBuilder(); builder.RegisterModule(new ImplementationModule()); var container = builder.Build();
      
      





シンプルなインジェクター



   .
      
      





構造マップ



 public class ImplementationModule : Registry { public ImplementationModule() { For<IAuthorRepository>().Use<AuthorRepositoryCtro>(); For<IBookRepository>().Use<BookRepositoryCtro>(); For<ILog>().Use<ConsoleLog>(); } } /********* * usage * *********/ var registry = new Registry(); registry.IncludeRegistry<ImplementationModule>(); var container = new Container(registry);
      
      





Ninject



 public class ImplementationModule : NinjectModule { public override void Load() { Bind<IAuthorRepository>().To<AuthorRepositoryCtro>(); Bind<IBookRepository>().To<BookRepositoryCtro>(); Bind<ILog>().To<ConsoleLog>(); } } /********* * usage * *********/ var container = new StandardKernel(new ImplementationModule());
      
      





団結



 public class ImplementationModule : UnityContainerExtension { protected override void Initialize() { Container.RegisterType<IAuthorRepository, AuthorRepositoryCtro>(); Container.RegisterType<IBookRepository, BookRepositoryCtro>(); Container.RegisterType<ILog, ConsoleLog>(); } } /********* * usage * *********/ var container = new UnityContainer(); container.AddNewExtension<ImplementationModule>();
      
      





キャッスルウィンザー



 public class ImplementationModule : IWindsorInstaller { public void Install(IWindsorContainer container, IConfigurationStore store) { container.Register(Component.For<IAuthorRepository>().ImplementedBy<AuthorRepositoryCtro>()); container.Register(Component.For<IBookRepository>().ImplementedBy<BookRepositoryCtro>()); container.Register(Component.For<ILog>().ImplementedBy<ConsoleLog>()); } } /********* * usage * *********/ var container = new WindsorContainer(); container.Install(new ImplementationModule());
      
      





PS



以下のテキストでは、ライフタイムスコープ管理と高度な機能について検討します。








All Articles