メインコンテンツまでスキップ

DIコンテナの使い方

土井
ディアシステム(株)開発一部第2課

別記事にて依存関係逆転の原則とDI(Dependency Injection)の手法について記載いたしました。今回はこの手法を使うときに便利なDIコンテナの使い方を紹介します。

前提条件

以下のツールを事前にインストールしておいてください

  • Visual Studio また以下のライブラリをNugetパッケージよりインストールしてください
  • Microsoft.Extensions.DependencyInjection
動作環境
  • Visual Studio Community 2022
  • .NET6.0

早速DIコンテナを使ってみる

まず使用するインターフェースとクラスを用意します。

public interface ILogger
{
void Info(string message);
}

public class Logger : ILogger
{
public void Info(string message)
{
Console.WriteLine(message)
}
}

次にDIコンテナを作成します。
DIコンテナは「ServiceCollection」クラスを使用します。
この「ServiceCollection」クラスはDIの情報を格納するクラスで、ここにインターフェースとクラスを登録していきます。
※TestFuncClassはこの後、作成します。

using Microsoft.Extensions.DependencyInjection;

・・・
public class Program
{

static void Main(string[] args)
{
var services = new ServiceCollection();
services.AddSingleton<ILogger, Logger>();

TestFuncClass inst = new TestFuncClass();
inst.Execute(services);
}
}

インターフェースとクラスの登録には3種類のメソッドが用意されており、
状況によって使い分けます。各メソッドの使い方等については後述にて記載いたします。

  • AddSingleton アプリケーション全体で一度だけインスタンスが生成され、全てのリクエストで同じインスタンスが再利用されます。

  • AddScoped 各スコープごとに新しいインスタンスが生成されます。

  • AddTransient 各リクエストごとに新しいインスタンスが生成されます。

次にこのDIコンテナを使ったクラスを作成します。
BuildServiceProvider()メソッドはServiceCollectionに登録されたサービスのインスタンスを解決し、提供する役割を持ちます。
Executeメソッド内ではBuildServiceProvider()からGetServiceメソッドを使って、ILoggerを継承したLoggerのインスタンスを受け取ります。これでTestFuncClassはILoggerを継承したLoggerクラスのメソッドを使用することができます。

public class TestFuncClass
{
public void Execute(ServiceCollection services)
{
var serviceProvider = services.BuildServiceProvider();
var logger = serviceProvider.GetService<ILogger>();
logger.Info(“test”);
}
}

またLogger内にstring型の引数を持つコンストラクタがある場合、インスタンスとインターフェースの登録は以下のようになります。

public class Logger : ILogger
{
private string _subMessage;
public Logger_Part2(string message)
{
_subMessage = message;
}
public void Info(string message)
{
Console.WriteLine(message + _subMessage);
}
}

public class Program
{
static void Main(string[] args)
{
TestFuncClass inst = new TestFuncClass();
var services = new ServiceCollection();

services.AddSingleton<ILogger>(provider => new Logger(“追加メッセージ”));
inst.Execute(services);
}
  }

DIコンテナへの登録の3種類の確認
前述にてDIコンテナへ登録する際に3つのメソッドがあることを記載しました。
ここではその使い方や動作の違いを確認していきたいと思います。

ILoggerとLoggerクラスを以下のように修正します。

public interface ILogger
{
stirng SubMessage{ get; set;}
void Info(string message);
}

public class Logger : ILogger
{
public string SubMessage { get; set; }
public void Info(string message)
{
Console.WriteLine(message)
}
}
  • AddSingleton
    アプリケーション全体で一度だけインスタンスが生成されるので、インスタンスの受け取りを再度行っても、同じインスタンスが取得されlogger.SubMessageに入れた値がそのままコンソールへ表示されています。
public class Program
{
Static void Main(string[] args)
{
var services = new ServiceCollection();
// DIコンテナへの登録
services.AddSingleton<ILogger,Logger>();

var serviceProvider = services.BuildServiceProvider();
// インスタンス受け取り
var logger = serviceProvider.GetService<ILogger>();
logger.SubMessage = “追加メッセージ”;
logger.Info(“test ++ logger.SubMessage);
// もう一度インスタンスを受け取る
logger = serviceProvider.GetService<ILogger>();
logger.Info(“test ++ logger.SubMessage);
}
}

出力結果

test +追加メッセージ
test +追加メッセージ
  • AddScoped
    AddScopedでは各スコープごとに新しいインスタンスが生成されます。
    使い方としてはCreateScope()メソッドによって、スコープを作成し、Dispose()メソッドを使うことで、スコープを破棄します。
    このスコープを破棄しない限りはGetService()メソッドを実行しても、同じインスタンスが返されます。 スコープを再作成すると、新しいインスタンスが生成されます。
public class Program
{
Static void Main(string[] args)
{
var services = new ServiceCollection();
// DIコンテナへの登録
services. AddScoped<ILogger,Logger>();
var serviceProvider = services.BuildServiceProvider();
// スコープの作成
var scope = serviceProvider.CreateScope();
        // インスタンス受け取り
var logger = scope.ServiceProvider.GetService<ILogger>();
logger.SubMessage = “追加メッセージ”;
logger.Info(“test ++ logger.SubMessage);
// もう一度インスタンスを受け取る
logger = scope.ServiceProvider.GetService<ILogger>();
logger.Info(“test ++ logger.SubMessage);
// スコープの破棄
Scope.Dispose();

// スコープの再作成
scope = serviceProvider.CreateScope();
logger = scope.ServiceProvider.GetService<ILogger>();
logger.Info(“test ++ logger.SubMessage);
// スコープの破棄
Scope.Dispose();
}
}

出力結果

test +追加メッセージ
test +追加メッセージ
test +
  • AddTransient
    リクエストのたびに新しいインスタンスが生成されるため、GetService()の実行のたびに新しいインスタンスが生成されます。そのため2つ目の出力の際、logger.SubMessageに入れた値は空の状態となっています。
public class Program
{
Static void Main(string[] args)
{
var services = new ServiceCollection();
// DIコンテナへの登録
services. AddTransient<ILogger,Logger>();
var serviceProvider = services.BuildServiceProvider();
// インスタンス受け取り
var logger = serviceProvider.GetService<ILogger>();
logger.SubMessage = “追加メッセージ”;
logger.Info(“test ++ logger.SubMessage);
// もう一度インスタンスを受け取る
logger = serviceProvider.GetService<ILogger>();
logger.Info(“test ++ logger.SubMessage);
}
}

出力結果

test +追加メッセージ
test +

以上がDIコンテナ(Microsoft.Extensions.DependencyInjection)の使い方です。
今後もこの機能を使って、依存関係逆転の原則に沿った実装を行っていきたいと思います。