DI(依存性注入) とは
Dependency Injection は日本語で依存性注入、あるいは略して DI と呼ばれます。
DIはコンポーネント間における依存関係を薄くすることで、単体テストをやりやすくしたり、他機能への依存度を低下させてコンポーネント化を促進する狙いがあります。
この記事ではC#で ServiceCollection を使用したDIの実現方法をまとめます。
ServiceCollection で DI(依存性注入)
前述の通りテストしやすいコードや保守性、拡張性の高いコードを書くためには DI(依存性注入) を使うとよいです。
C#だと ServiceCollection クラスが提供されており、これを使って実現可能です。
ServiceCollection クラス (Microsoft.Extensions.DependencyInjection) | Microsoft Docs
ServiceCollection はその名の通り、サービスのコレクションです。ここで言うサービスは単なるクラスくらいのイメージです。
ServiceCollection クラスの使い方
ServiceCollection を使用するには Microsoft.Extensions.DependencyInjection.dll を参照する必要があります。
アプリケーション起動時に ServiceCollection に必要なクラスを登録し、必要な個所で ServiceCollection からインスタンスを取得するような感じで使用します。
基本の使い方
ServiceCollectionを生成ServiceCollectionにクラスと生成方法を登録する- 使用したい箇所で
ServiceProviderからインスタンスを取得する
例えば IMyService インターフェースとそれを実装した MyService があるとします。
interface IMyService { }
class MyService: IMyService
{
public MyService()
{
Console.WriteLine("MyService コンストラクタ");
}
}
このクラスをDIできるようにします。
// 1, 2: IMyService を登録(実際に使用するのは MyService)
var serviceCollection = new ServiceCollection();
serviceCollection.AddTransient<IMyService, MyService>();
// ..
// 3: サービスプロバイダから型(IMyService)に対応するインスタンスを取得
var serviceProvider = serviceCollection.BuildServiceProvider();
var myService = provider.GetService<IMyService>();
こんな感じで使用します。
ServiceCollection もしくは ServiceProvider のインスタンスは使用したい箇所まで何とかして持ってくる必要はあります。ServiceCollection は IServiceCollection を、ServiceProvider は IServiceProvider を実装しているので、引数でバケツリレーして持ちまわるのが良いでしょうか。
インスタンスの生成方法いろいろ
拡張メソッド を使って ServiceCollection に様々なインスタンス生成方法で登録ができます。
インスタンスを都度生成する
Provider からインスタンスを取得する際に都度生成するようにするには、ServiceCollection.AddTransient() を使用します。
interface IMyService { }
class MyService: IMyService
{
public MyService()
{
Console.WriteLine("MyService コンストラクタ");
}
}
// ..
var serviceCollection = new ServiceCollection();
// 都度インスタンスを生成する方法で登録
serviceCollection.AddTransient<IMyService, MyService>();
var serviceProvider = serviceCollection.BuildServiceProvider();
// 都度生成された別インスタンスが取得される
var myService1 = serviceProvider.GetService<IMyService>();
var myService2 = serviceProvider.GetService<IMyService>();
コンストラクタが2回呼び出されていることが確認できます。
生成されたインスタンスを使いまわす
ServiceCollection.AddSingleton() を使用して登録すると、一度生成したインスタンスを使いまわすことができます。文字通りシングルトン的に扱えます。
serviceCollection.AddSingleton<IMyService, MyService>();
生成されるのは初回にインスタンスを取得するタイミングとなります。何度呼び出されても同じインスタンスを返します。
生成用のファクトリ関数を指定する
単純にインスタンス化するのではなく、複雑な初期化が必要な場合はファクトリ関数を指定して生成させることが可能です。
Func<IServiceProvider, TImplementation> を引数に渡します。これがプロバイダを引数に受け取り、実装された具体的な型を返すファクトリ関数です。使い方は以下の通りです。
serviceCollection.AddTransient<IMyService>(provider => new MyService());
ここでは単純に初期化だけしてますが、プロバイダから別のサービスクラスを呼び出して色々することもできます。AddTransient() を使ていれば都度この関数が呼ばれることになります。
ほかにもいろいろ AddXxx() みたいなのがあるみたいですが、基本は上記で事足りるかなと思います。
インスタンスの取得方法いろいろ
ServiceProvider
ServiceProvider クラス (Microsoft.Extensions.DependencyInjection) | Microsoft Docs
ServiceProvider は ServiceCollection に登録したクラスを取得するためのクラスです。
任意のT型のインスタンスを取得
GetService<T>() で T 型のインスタンスを取得します。未登録の場合は NULL が取得されます。
var myService = provider.GetService<IMyService>();
任意のT型のインスタンスをすべて列挙
GetServices<T>() で T 型のインスタンスを列挙(IEnumerable<T>)します。
interface IMyService { }
class MyService1 : IMyService { }
class MyService2 : IMyService { }
// ..
var serviceCollection = new ServiceCollection();
serviceCollection.AddTransient<IMyService, MyService1>();
serviceCollection.AddTransient<IMyService, MyService2>();
var provider = serviceCollection.BuildServiceProvider();
// サービスを列挙
var services = provider.GetServices<IMyService>();
同じクラスのインスタンスでも問題なく動きました。
GetRequiredService で必須
GetRequiredService<T>() とすると、T型のインスタンスを取得します。ただし GetService<T>() の場合と異なり、存在しない場合に例外が発生します。
GetRequiredService と GetRequiredServices があります。
以上。
ここまで具体的にやり方を書いてあると非常にわかりやすいです!
ところで、
生成されたインスタンスを使いまわす
ServiceCollection.AddTransient() を使用して登録すると、一度生成したインスタンスを使いまわすことができます。文字通りシングルトン的に扱えます。
serviceCollection.AddTransient();
の箇所ってAddTransientではない認識でよろしいですか?(AddSingleton ??)
コメントありがとうございます。
ご指摘の通り、「生成されたインスタンスを使いまわす」の箇所については “AddSingleton” が正しいです。
記事の内容を修正しました。