一文で依存性注入を説明する#
まずは自己催眠を行い、依存性注入はデザインパターンの一つであることを覚えておきましょう。
簡単な例を見てみましょう:
// 1. 強い依存関係
public int MyStringTool(){
string str = "Hello world!";
return str.Count();
}
// 2. 依存関係なし
string str = "Hello world!";
public int MyStringTool(string str){
return str.Count();
}
二つの関数があり、一つは関数内部でローカル変数を作成し、もう一つは引数を通じて変数を渡します。前者は変数と関数が強い依存関係を持ち(ローカル変数は関数内部でのみ機能します)、後者は変数が引数として関数に渡され、関数は一般化された処理能力を持ち、変数と関数の間に依存関係はありません。
これが依存性注入の基本的な考え方です。クラス間の派生は以下の例に示されています:
// 1. 強い依存関係
public class MainObject{
public Method(){
var tool = new MyTool();
// toolを使用
}
}
// 2. 依存性注入
public class MainObject{
public readonly MyTool _tool;
MainObject(MyTool tool){
_tool = tool;
}
public Method(){
_tool.function();
// toolを使用
}
}
ツールクラス Tool を呼び出す二つの方法があります。第一の方法はクラス内部でツール MyTool をインスタンス化し、両者は強い依存関係を持ちます。第二の方法はコンストラクタを通じてツール MyTool のインスタンスを渡し、依存性注入を実現します。つまり、依存項目 MyTool がコンストラクタを通じて注入されるのです。
もう一つの用語は制御の反転です。この言葉の方が適切だと思います。
制御は二者間の依存関係を表し、誰が誰を制御するか / 誰が誰に依存するか(存在)を示します。
最初の関数の例では、変数と関数の関係は 変数が関数に依存して存在する / 関数が変数のライフサイクルを制御する から 関数がパラメータを提供しなければ実行できない に変わります。
第二のクラスの例では、ツールクラスと主体クラスの関係は ツールインスタンスが主体クラスによって作成され、主体クラスに依存する から 主体クラスのコンストラクタがツールクラスを提供しなければならない に変わります。これが反転の意味を示しています。もちろん、クラスにはデフォルトのコンストラクタがあり、必ずしもツールクラスを提供する必要はありません。
ASP.NET における依存性注入#
ASP.NET の主体は WebApplication
クラスです。そのツールクラスは一連の Services
です。以下は典型的な ASP.NET アプリケーションのエントリプログラム(Program.cs)です。
// Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
var provider = builder.Services.BuildServiceProvider();
var app = builder.Build();
app.UseAuthorization();
app.MapControllers();
app.Run();
このサンプルプログラムでは、Services
は依存性注入の方法で WebApplication
にサービスを提供しています。
この Web アプリケーションには、App に機能サポートを提供する独立したコンテナがあります。これらのコンテナは、コンストラクタ注入の方法で互いにインスタンスを取得できます。以下は依存性注入(DI)に関与する重要なクラスとインターフェースです:
-
IServiceCollection:これはサービスを登録するためのインターフェースです。さまざまなサービスタイプと実装を登録するために、その拡張メソッドを使用できます。
-
IServiceProvider:これは登録されたサービスインスタンスを解決し取得するためのサービスプロバイダーインターフェースです。必要に応じてコンテナによって作成されます。
-
ServiceLifetime:これは登録されたサービスのライフサイクルを指定するための列挙型です。一般的なライフサイクルオプションには Singleton(シングルトン)、Scoped(スコープ)、および Transient(トランジェント)が含まれます。
-
AddTransient、AddScoped および AddSingleton:これらは
IServiceCollection
インターフェースの拡張メソッドで、サービスタイプとその実装をサービスコンテナに追加し、それらのライフサイクルを指定します。 -
IServiceProvider.GetService<T>:これは
IServiceProvider
インターフェースのメソッドで、登録されたサービスインスタンスを取得します。サービスタイプのジェネリックパラメータを指定することで、特定のタイプのサービスインスタンスを取得できます。 -
コンストラクタ注入:これは一般的な依存関係注入の方法で、依存するサービスをクラスのコンストラクタに引数として渡すことで実現されます。依存項目はクラスインスタンスを作成する際に自動的に解決され提供されます。
-
プロパティ注入:コンストラクタ注入に加えて、プロパティ注入を使用して依存項目を注入することもできます。クラス内で注入が必要なプロパティを定義し、対応するプロパティ注入属性(例:
[Inject]
、[Autowired]
など)でマークします。
Tip.
Services.AddXXX()
は依存コンテナに依存項目を追加するためのメソッドで、これらのメソッドは C# の拡張メソッドの構文特性を提供します。この構文は、クラスを変更することなく追加のメソッドを追加できます:// 名前空間は MyNamespace public static int ExMethod(this String str){ return str.Count(); } // 使用 using MyNamespace; String str = new String("hello world!"); str.ExMethod();
.NET では、サービスはアプリケーションの依存項目であり、依存性注入(DI)を使用して管理できます。サービスはアプリケーション内で定義されたクラス、インターフェース、またはその他のコンポーネントであり、特定の機能やサービスを提供します。
クラスが他のサービスを使用する必要がある場合、コンストラクタ注入の方法でこれらの依存項目を直接引き入れることができます。依存項目をコンストラクタパラメータとして宣言することで、DI コンテナはこれらの依存項目を自動的に検出し、クラスのインスタンスを作成する際に自動的に提供します。
例:
public class MyService
{
private readonly IAnotherService _anotherService;
public MyService(IAnotherService anotherService)
{
_anotherService = anotherService;
}
public void DoSomething()
{
// 依存項目を使用して操作
_anotherService.SomeMethod();
}
}
上記の例では、MyService
クラスは IAnotherService
インターフェースの実装を使用する必要があります。IAnotherService
をコンストラクタパラメータとして宣言することで、DI コンテナは MyService
のインスタンスを作成する際に自動的に提供します。
外部(静的)クラスやメソッドが依存項目を使用する必要がある場合、IServiceProvider
インターフェースを通じてそれらを取得できます。IServiceProvider
は登録されたサービスインスタンスを解決し取得するためのサービスプロバイダーインターフェースです。
例:
public static class MyStaticClass
{
public static void DoSomething(IServiceProvider serviceProvider)
{
// IServiceProviderを通じて依存項目を取得
var anotherService = serviceProvider.GetService<IAnotherService>();
// 依存項目を使用して操作
anotherService.SomeMethod();
}
}
上記の例では、MyStaticClass
は静的クラスであり、そのメソッド DoSomething
は IAnotherService
のインスタンスを使用する必要があります。IServiceProvider
を DoSomething
メソッドにパラメータとして渡すことで、GetService
メソッドを使用して IServiceProvider
から IAnotherService
のインスタンスを取得できます。
まとめると、依存性注入と IServiceProvider
を通じて、コンストラクタ内で依存項目を直接引き入れ、DI コンテナが自動的に提供することができます。外部(静的)クラスやメソッドについては、IServiceProvider
を通じて依存項目を取得し、依存項目の使用と解耦を実現できます。
依存項目のライフサイクル#
Services
を依存コンテナで統一管理する際、特定の依存項目が必要なときにコンテナから提供されます。これにより、依存項目はいつインスタンス化されるのかという問題が生じます。必要なときに再作成するのか、インスタンスを永続化するのか、リクエスターのスコープに応じてインスタンスを提供するのか、これが依存項目のライフサイクルです。
依存性注入(DI)コンテナ内では、インスタンスの作成は設定や必要なタイミングに応じて異なる処理が行われます。一般的に、DI コンテナはアプリケーションの起動時に初期化され、必要に応じていくつかのインスタンスを事前に作成して登録します。具体的な作成タイミングとライフサイクルは設定によって制御できます。
一般的なインスタンスライフサイクルには以下があります:
- Singleton(シングルトン):アプリケーション全体のライフサイクルで一つのインスタンスを作成し、必要に応じて再利用します。このインスタンスは通常、アプリケーションの起動時に作成されます。
- Scoped(スコープ):各スコープ(例えば各リクエスト)内で一つのインスタンスを作成し、そのスコープ内で再利用します。各スコープが終了する際に、インスタンスは破棄されます。
- Transient(トランジェント):リクエストごとに新しいインスタンスを作成します。各リクエストは新しいインスタンスを受け取り、リクエストが完了した後に破棄されます。
これらのライフサイクルオプションにより、アプリケーションのニーズに応じてインスタンスの作成とライフサイクルを管理できます。サービスを登録する際に、必要なライフサイクルオプションを指定できます。例えば、AddSingleton
、AddScoped
、または AddTransient
メソッドを使用します。
その他#
ASP.NET Core がデフォルトで提供する依存性注入コンテナに加えて、他のサードパーティの依存性注入コンテナを使用することもできます。以下は一般的な依存性注入コンテナのいくつかです:
- Autofac: Autofac は人気のある強力な依存性注入コンテナで、多くの高度な機能と拡張ポイントを提供し、さまざまなアプリケーションシナリオに適しています。
- Unity: Unity は Microsoft が提供する軽量の依存性注入コンテナで、優れたパフォーマンスと拡張性を持ち、インターフェース指向の開発をサポートしています。
- Ninject: Ninject はもう一つの人気のある依存性注入コンテナで、シンプルさと使いやすさに重点を置き、オブジェクト間の依存関係を管理するためのシンプルで柔軟な方法を提供します。
- StructureMap: StructureMap は強力な依存性注入コンテナで、コンストラクタ注入、プロパティ注入、メソッド注入をサポートし、インターセプターや設定ファイルなどの多くの高度な機能を提供します。
これらの依存性注入コンテナはそれぞれ特徴と利点があり、プロジェクトのニーズや個人の好みに応じて適切なコンテナを選択できます。これらのコンテナを使用する際は、それぞれのドキュメントや例に従って適切な設定と使用を行う必要があります。
注意が必要なのは、サードパーティの依存性注入コンテナを使用する場合、関連する NuGet パッケージをインストールし、適切な設定を行う必要があることです。また、これらのコンテナの特定の構文や使用法を理解し、依存項目を正しく登録および解決する必要があります。
要するに、ASP.NET Core のデフォルトの依存性注入コンテナは、ほとんどのアプリケーションのニーズを満たすのに十分です。しかし、より高度な機能や特定のニーズがある場合は、他の依存性注入コンテナの使用を検討することができます。