チュートリアル: .NET で依存関係の挿入を使用する

このチュートリアルでは、.NET で依存関係の挿入 (DI) を使用する方法について説明します。 Microsoft 拡張機能では、DI はサービスを追加し、IServiceCollection で構成することで管理されます。 IHost インターフェイスには IServiceProvider インスタンスが公開されています。これは、登録されたすべてのサービスのコンテナーとして機能します。

このチュートリアルでは、以下の内容を学習します。

  • 依存関係の挿入を使用する .NET コンソール アプリを作成する
  • 汎用ホストを構築して構成する
  • いくつかのインターフェイスとそれに対応する実装を記述する
  • DI のサービス有効期間とスコープを使用する

前提条件

  • .NET Core 3.1 SDK 以降。
  • 新しい .NET アプリケーションの作成と NuGet パッケージのインストールに関する知識。

新しいコンソール アプリケーションを作成する

dotnet new コマンドまたは IDE の新しいプロジェクト ウィザードのいずれかを使用して、ConsoleDI.Example という名前の新しい .NET コンソール アプリケーションを作成します。 NuGet パッケージ Microsoft.Extensions.Hosting をプロジェクトに追加します。

新しいコンソール アプリ プロジェクト ファイルは次のようになります。

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>true</ImplicitUsings>
    <RootNamespace>ConsoleDI.Example</RootNamespace>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
  </ItemGroup>

</Project>

重要

この例では、アプリをビルドして実行するには、Microsoft.Extensions.Hosting NuGet パッケージが必要です。 一部のメタパッケージには Microsoft.Extensions.Hosting パッケージが含まれている場合があります。その場合、明示的なパッケージ参照は必要ありません。

インターフェイスを追加する

このサンプル アプリでは、依存関係を挿入してサービスの有効期間を処理する方法について説明します。 さまざまなサービスの有効期間を表す複数のインターフェイスを作成します。 次のインターフェイスをプロジェクトのルート ディレクトリに追加します。

IReportServiceLifetime.cs

using Microsoft.Extensions.DependencyInjection;

namespace ConsoleDI.Example;

public interface IReportServiceLifetime
{
    Guid Id { get; }

    ServiceLifetime Lifetime { get; }
}

IReportServiceLifetime インターフェイスは、以下を定義します。

  • サービスの一意識別子を表す Guid Id プロパティ。
  • サービスの有効期間を表す ServiceLifetime プロパティ。

IExampleTransientService.cs

using Microsoft.Extensions.DependencyInjection;

namespace ConsoleDI.Example;

public interface IExampleTransientService : IReportServiceLifetime
{
    ServiceLifetime IReportServiceLifetime.Lifetime => ServiceLifetime.Transient;
}

IExampleScopedService.cs

using Microsoft.Extensions.DependencyInjection;

namespace ConsoleDI.Example;

public interface IExampleScopedService : IReportServiceLifetime
{
    ServiceLifetime IReportServiceLifetime.Lifetime => ServiceLifetime.Scoped;
}

IExampleSingletonService.cs

using Microsoft.Extensions.DependencyInjection;

namespace ConsoleDI.Example;

public interface IExampleSingletonService : IReportServiceLifetime
{
    ServiceLifetime IReportServiceLifetime.Lifetime => ServiceLifetime.Singleton;
}

IReportServiceLifetime のすべてのサブインターフェイスは、既定で IReportServiceLifetime.Lifetime を明示的に実装します。 たとえば、IExampleTransientServiceServiceLifetime.Transient 値を使用して IReportServiceLifetime.Lifetime を明示的に実装します。

既定の実装を追加する

実装例では、Guid.NewGuid() の結果を使用して Id プロパティをすべて初期化します。 さまざまなサービスの次の既定の実装クラスをプロジェクト ルート ディレクトリに追加します。

ExampleTransientService.cs

namespace ConsoleDI.Example;

internal sealed class ExampleTransientService : IExampleTransientService
{
    Guid IReportServiceLifetime.Id { get; } = Guid.NewGuid();
}

ExampleScopedService.cs

namespace ConsoleDI.Example;

internal sealed class ExampleScopedService : IExampleScopedService
{
    Guid IReportServiceLifetime.Id { get; } = Guid.NewGuid();
}

ExampleSingletonService.cs

namespace ConsoleDI.Example;

internal sealed class ExampleSingletonService : IExampleSingletonService
{
    Guid IReportServiceLifetime.Id { get; } = Guid.NewGuid();
}

各実装は internal sealed として定義され、それに対応するインターフェイスを実装します。 たとえば、ExampleSingletonServiceIExampleSingletonService を実装します。

DI を必要とするサービスを追加する

次のサービスの有効期間レポーターを追加します。これは、コンソール アプリに対するサービスとして機能します。

ServiceLifetimeReporter.cs

namespace ConsoleDI.Example;

internal sealed class ServiceLifetimeReporter(
    IExampleTransientService transientService,
    IExampleScopedService scopedService,
    IExampleSingletonService singletonService)
{
    public void ReportServiceLifetimeDetails(string lifetimeDetails)
    {
        Console.WriteLine(lifetimeDetails);

        LogService(transientService, "Always different");
        LogService(scopedService, "Changes only with lifetime");
        LogService(singletonService, "Always the same");
    }

    private static void LogService<T>(T service, string message)
        where T : IReportServiceLifetime =>
        Console.WriteLine(
            $"    {typeof(T).Name}: {service.Id} ({message})");
}

ServiceLifetimeReporter には、前述の各サービス インターフェイス (つまり、IExampleTransientServiceIExampleScopedService、および IExampleSingletonService) を必要とするコンストラクターを定義します。 このオブジェクトによって、指定された lifetimeDetails パラメーターを使用してコンシューマーがサービスを報告できるようにする 1 つのメソッドが公開されます。 ReportServiceLifetimeDetails メソッドを呼び出すと、各サービスの一意識別子がサービスの有効期間のメッセージと共にログされます。 ログ メッセージは、サービスの有効期間を視覚化するのに役立ちます。

DI 用のサービスを登録する

次のコードを使用して Program.cs を更新します。

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using ConsoleDI.Example;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Services.AddTransient<IExampleTransientService, ExampleTransientService>();
builder.Services.AddScoped<IExampleScopedService, ExampleScopedService>();
builder.Services.AddSingleton<IExampleSingletonService, ExampleSingletonService>();
builder.Services.AddTransient<ServiceLifetimeReporter>();

using IHost host = builder.Build();

ExemplifyServiceLifetime(host.Services, "Lifetime 1");
ExemplifyServiceLifetime(host.Services, "Lifetime 2");

await host.RunAsync();

static void ExemplifyServiceLifetime(IServiceProvider hostProvider, string lifetime)
{
    using IServiceScope serviceScope = hostProvider.CreateScope();
    IServiceProvider provider = serviceScope.ServiceProvider;
    ServiceLifetimeReporter logger = provider.GetRequiredService<ServiceLifetimeReporter>();
    logger.ReportServiceLifetimeDetails(
        $"{lifetime}: Call 1 to provider.GetRequiredService<ServiceLifetimeReporter>()");

    Console.WriteLine("...");

    logger = provider.GetRequiredService<ServiceLifetimeReporter>();
    logger.ReportServiceLifetimeDetails(
        $"{lifetime}: Call 2 to provider.GetRequiredService<ServiceLifetimeReporter>()");

    Console.WriteLine();
}

services.Add{LIFETIME}<{SERVICE}> 拡張メソッドは、サービスを追加 (および場合によっては構成) します。 アプリをこの規則に従わせることをお勧めします。 公式の Microsoft パッケージを作成する場合を除き、Microsoft.Extensions.DependencyInjection 名前空間に拡張メソッドを配置しないでください。 Microsoft.Extensions.DependencyInjection 名前空間内で定義されている拡張メソッドは:

  • 追加の using ブロックを必要とせずに IntelliSense に表示されます。
  • これらの拡張メソッドが通常呼び出される Program または Startup クラスで、必要な using ステートメントの数を減らします。

このアプリによって次のことが行われます。

  • ホスト ビルダー設定を使用して IHostBuilder インスタンスを作成します。
  • サービスを構成し、対応するサービスの有効期間を指定してそれらを追加する。
  • Build() を呼び出して、IHost のインスタンスを割り当てる。
  • ExemplifyScoping を呼び出して、IHost.Services を渡す。

まとめ

このサンプル アプリでは、いくつかのインターフェイスと対応する実装を作成しました。 これらの各サービスは一意に識別され、ServiceLifetime とペアになっています。 サンプル アプリでは、インターフェイスに対してサービス実装を登録する方法と、インターフェイスをバッキングせずに純粋なクラスを登録する方法を示します。 次に、サンプル アプリでは、コンストラクター パラメーターとして定義されている依存関係を実行時に解決する方法を示します。

アプリが実行されると、以下に示すような出力が表示されます。

// Sample output:
// Lifetime 1: Call 1 to provider.GetRequiredService<ServiceLifetimeReporter>()
//     IExampleTransientService: d08a27fa-87d2-4a06-98d7-2773af886125 (Always different)
//     IExampleScopedService: 402c83c9-b4ed-4be1-b78c-86be1b1d908d (Changes only with lifetime)
//     IExampleSingletonService: a61f1ff4-0b14-4508-bd41-21d852484a7b (Always the same)
// ...
// Lifetime 1: Call 2 to provider.GetRequiredService<ServiceLifetimeReporter>()
//     IExampleTransientService: b43d68fb-2c7b-4a9b-8f02-fc507c164326 (Always different)
//     IExampleScopedService: 402c83c9-b4ed-4be1-b78c-86be1b1d908d (Changes only with lifetime)
//     IExampleSingletonService: a61f1ff4-0b14-4508-bd41-21d852484a7b (Always the same)
// 
// Lifetime 2: Call 1 to provider.GetRequiredService<ServiceLifetimeReporter>()
//     IExampleTransientService: f3856b59-ab3f-4bbd-876f-7bab0013d392 (Always different)
//     IExampleScopedService: bba80089-1157-4041-936d-e96d81dd9d1c (Changes only with lifetime)
//     IExampleSingletonService: a61f1ff4-0b14-4508-bd41-21d852484a7b (Always the same)
// ...
// Lifetime 2: Call 2 to provider.GetRequiredService<ServiceLifetimeReporter>()
//     IExampleTransientService: a8015c6a-08cd-4799-9ec3-2f2af9cbbfd2 (Always different)
//     IExampleScopedService: bba80089-1157-4041-936d-e96d81dd9d1c (Changes only with lifetime)
//     IExampleSingletonService: a61f1ff4-0b14-4508-bd41-21d852484a7b (Always the same)

アプリの出力から、次のことを確認できます。

  • Transient サービスは常に異なり、サービスを取得するたびに新しいインスタンスが作成される。
  • Scoped サービスはスコープが新しくなった場合にのみ変更されますが、スコープ内では同じインスタンスである。
  • Singleton サービスは常に同じであり、新しいインスタンスは一度作成されるだけである。

関連項目