本文へスキップ
BecomeCoder

C# MVVMコース · 第8章 周辺の道具立て ― DI・ヘルパー・アンチパターン · レッスン30

DIコンテナの考え方 ― 手動DIから自動化へ

ローカル実施

導入

レッスン15TaskServiceITaskRepositoryをコンストラクタで注入しました(コンストラクタインジェクション。詳しくはC#デザインパターンコースのレッスン19)。ただしアプリの依存が増えてくると、「誰が何をnewして、どの順で渡すか」を手であちこち書くのは大変になります。ASP.NET Coreなどが標準採用しているDIコンテナは、この組み立てを自動化する道具です。

説明

flowchart LR
    R["登録<br/>services.AddSingleton&lt;ITaskRepository, InMemoryTaskRepository&gt;()"] --> C["コンテナ<br/>IServiceProvider"]
    C -->|解決| G["provider.GetRequiredService&lt;TaskListViewModel&gt;()"]
    G --> V["依存が全部組み立てられた<br/>TaskListViewModel"]
    style C fill:#e1f5fe

Microsoft.Extensions.DependencyInjection(NuGetパッケージ)を使うと、次のように書けます。

// App.xaml.cs や Program.cs の起動処理(読み物:NuGetが必要でブラウザでは実行できません)
var services = new ServiceCollection();

// 登録: 「この抽象が要求されたら、この実装を使う」を宣言するだけ
services.AddSingleton<ITaskRepository, InMemoryTaskRepository>();
services.AddSingleton<ITaskService, TaskService>();
services.AddTransient<TaskListViewModel>();

var provider = services.BuildServiceProvider();

// 解決: TaskListViewModel が要求する ITaskService も、
//       ITaskService が要求する ITaskRepository も、まとめて自動で組み立てられる
var viewModel = provider.GetRequiredService<TaskListViewModel>();
  • コンテナは各クラスコンストラクタを見て、必要な依存を再帰的に解決する。TaskListViewModelITaskServiceを、ITaskService(の実体TaskService)がITaskRepositoryを要求していることまで自動で辿ってくれる
  • Add系のメソッドは寿命(ライフタイム)も決める
    • AddSingleton: アプリ全体で1つのインスタンスを使い回す。ITaskRepositoryITaskServiceはデータを共有し続けたいのでこちら
    • AddTransient: 要求されるたびに新しいインスタンスを作る。TaskListViewModelは画面を開くたびに新しい状態を持たせたいのでこちら
    • AddScoped: 1つの「スコープ」(Webアプリなら1リクエスト)の間だけ同じインスタンス。デスクトップアプリのMVVMではあまり使わない
  • 手でnewを書く場合との違いは「自動化」だけで、DIそのものの考え方(抽象への依存)は変わらない

このサイトのブラウザ環境にはNuGetが無いため、次のレッスンでは同じ考え方を手動で体験します。

まとめ

  • DIコンテナは「登録(この抽象にはこの実装)→ 解決(コンストラクタを辿って自動で組み立てる)」を自動化する道具
  • ライフタイムにはSingleton(アプリ全体で共有)・Transient(毎回新規)・Scoped(スコープ単位)がある
  • コンテナが無くても、DIという設計そのもの(抽象への依存・コンストラクタインジェクション)は変わらない

次回: 実際に依存を手で組み立てる場所、Composition Rootです。