.NET での依存関係の挿入の基礎について理解する

この記事では、ServiceCollection とそれに対応する ServiceProvider を手動で作成する .NET コンソール アプリを作成します。 依存関係の挿入 (DI) を使用してサービスを登録し解決する方法について学びます。 この記事では、Microsoft.Extensions.DependencyInjection NuGet パッケージを使用して、.NET の DI の基礎を示します。

Note

この記事では、Generic Host 機能を利用しません。 より包括的なガイドについては、「.NET で依存関係の挿入を使用する」をご覧ください。

作業の開始

開始するには、DI.Basics という名前の新しい .NET コンソール アプリケーションを作成します。 コンソール プロジェクトを作成するための最も一般的な方法の一部を次の一覧で参照してください。

プロジェクト ファイルの Microsoft.Extensions.DependencyInjection にパッケージ参照を追加する必要があります。 この方法に関係なく、プロジェクトが DI.Basics.csproj ファイルの次に示す XML のようになっていることを確認します。

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

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

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

</Project>

依存関係の挿入の基礎

依存関係の挿入は、ハードコーディングされた依存関係を削除し、アプリケーションの保守性とテスト性を高める設計パターンです。 DI は、クラスとその依存関係の間で Inversion of Control (IoC) を実現するための手法です。

.NET の DI の抽象クラスは、Microsoft.Extensions.DependencyInjection.Abstractions NuGet パッケージで定義されています。

  • IServiceCollection: サービス記述子のコレクションのコントラクトを指定します。
  • IServiceProvider: サービス オブジェクトを取得するためのメカニズムを定義します。
  • ServiceDescriptor: サービスの種類、実装、および有効期間を記述します。

.NET では、DI は IServiceCollection でサービスを追加し構成することによって管理されます。 サービスが登録された後、BuildServiceProvider メソッドを呼び出すことで IServiceProvider インスタンスがビルドされます。 IServiceProvider は、登録されているすべてのサービスのコンテナーとして機能し、サービスの解決に使用されます。

サンプル サービスを作成する

すべてのサービスが等しく作成されるわけではありません。 一部のサービスでは、サービス コンテナーが取得するたびに新しいインスタンス ("一時的") が必要です。一方で他のサービスでは、要求 ("スコープされた") またはアプリの有効期間全体 ("シングルトン") で共有する必要があります。 サービスの有効期間の詳細については、「サービスの有効期間」をご覧ください。

同様に、一部のサービスでは具象型のみを公開しますが、他のサービスではインターフェイスと実装型の間のコントラクトとして表されます。 これらの概念を示すのに役立つサービスのバリエーションをいくつか作成します。

"IConsole.cs" という名前の新しい C# ファイルを作成し、次のコードを追加します。

public interface IConsole
{
    void WriteLine(string message);
}

このファイルは、1 つのメソッド WriteLine を公開する IConsole インターフェイスを定義します。 次に、DefaultConsole.cs という名前の新しい C# ファイルを作成し、次のコードを追加します。

internal sealed class DefaultConsole : IConsole
{
    public bool IsEnabled { get; set; } = true;

    void IConsole.WriteLine(string message)
    {
        if (IsEnabled is false)
        {
            return;
        }

        Console.WriteLine(message);
    }
}

上記のコードは、IConsole インターフェイスの既定の実装を表しています。 WriteLine メソッドは、IsEnabled プロパティに基づいて条件付きでコンソールに書き込みます。

ヒント

実装の名前付けは、開発チームが同意する必要がある選択です。 Default プレフィックスは、インターフェイスの "既定の" 実装を示す一般的な規則ですが、必須 "ではありません"。

次に、IGreetingService.cs ファイルを作成し、次の C# コードを追加します。

public interface IGreetingService
{
    string Greet(string name);
}

次に、DefaultGreetingService.cs という名前の新しい C# ファイルを追加し、次のコードを追加します。

internal sealed class DefaultGreetingService(
    IConsole console) : IGreetingService
{
    public string Greet(string name)
    {
        var greeting = $"Hello, {name}!";

        console.WriteLine(greeting);

        return greeting;
    }
}

上記のコードは、IGreetingService インターフェイスの既定の実装を表しています。 サービスの実装には、プライマリ コンストラクター パラメーターとして IConsole が必要です。 Greet メソッド:

  • 与えられた namegreeting を作成します。
  • IConsole インスタンスで WriteLine メソッドを呼び出します。
  • 呼び出し元に greeting を返します。

作成する最後のサービスは "FarewellService.cs" ファイルです。続行する前に、次の C# コードを追加します。

public class FarewellService(IConsole console)
{
    public string SayGoodbye(string name)
    {
        var farewell = $"Goodbye, {name}!";

        console.WriteLine(farewell);

        return farewell;
    }
}

FarewellService は、インターフェイスではなく具象型を表します。 コンシューマーがアクセスできるように、public として宣言する必要があります。 internal および sealedとして宣言された他のサービス実装型とは異なり、このコードは、すべてのサービスがインターフェイスでなければならないことを示しています。 また、サービス実装を sealed で継承を防ぎ、internal でアセンブリへのアクセスを制限できることを示しています。

Program クラスを更新する

"Program.cs" ファイルを開き、既存のコンテンツを次の C# コードに置き換えます。

using Microsoft.Extensions.DependencyInjection;

// 1. Create the service collection.
var services = new ServiceCollection();

// 2. Register (add and configure) the services.
services.AddSingleton<IConsole>(
    implementationFactory: static _ => new DefaultConsole
    {
        IsEnabled = true
    });
services.AddSingleton<IGreetingService, DefaultGreetingService>();
services.AddSingleton<FarewellService>();

// 3. Build the service provider from the service collection.
var serviceProvider = services.BuildServiceProvider();

// 4. Resolve the services that you need.
var greetingService = serviceProvider.GetRequiredService<IGreetingService>();
var farewellService = serviceProvider.GetRequiredService<FarewellService>();

// 5. Use the services
var greeting = greetingService.Greet("David");
var farewell = farewellService.SayGoodbye("David");

上記の更新されたコードは、次の方法を示しています。

  • 新しい ServiceCollection インスタンスを作成します。
  • ServiceCollection でサービスを登録し構成します。
    • 実装ファクトリ オーバーロードを使用する IConsole は、IsEnabled が 'true に設定された DefaultConsole 型を返します。
    • IGreetingService は、 DefaultGreetingService 型の対応する実装型で追加されます。
    • FarewellService は具象型として追加されます。
  • ServiceCollection から ServiceProvider をビルドします。
  • IGreetingService サービスと FarewellService サービスを解決します。
  • 解決されたサービスを使用して、David という名前の人に挨拶し、別れを告げます。

DefaultConsoleIsEnabled プロパティを false に更新すると、Greet メソッドと SayGoodbye メソッドではコンソールへの結果のメッセージ書き込みが省略されます。 このような変更は、IConsole サービスが、アプリの動作に影響を与える "依存関係" として、IGreetingService サービスと FarewellService サービスに "挿入" されることを示すのに役立ちます。

これらのサービスはすべてシングルトンとして登録されますが、このサンプルでは、"一時的な" または "スコープされた" サービスとして登録されている場合と同じように機能します。

重要

この工夫された例では、サービスの有効期間は関係ありませんが、実際のアプリケーションでは、各サービスの有効期間を慎重に検討する必要があります。

サンプル アプリを実行する

サンプル アプリを実行するには、Visual Studio、Visual Studio Code で F5 キーを押すか、ターミナルで dotnet run コマンドを実行します。 アプリが完了すると、次の出力が表示されます。

Hello, David!
Goodbye, David!

サービス記述子

ServiceCollection にサービスを追加するために最も一般的に使用される API は、次のような有効期間名のジェネリック拡張メソッドです。

  • AddSingleton<TService>
  • AddTransient<TService>
  • AddScoped<TService>

これらのメソッドは、ServiceDescriptor インスタンスを作成し ServiceCollection に追加する便利なメソッドです。 ServiceDescriptor は、サービスの種類、実装の種類、有効期間を持つサービスを記述する単純なクラスです。 また、実装ファクトリとインスタンスを記述することもできます。

ServiceCollectionに登録した各サービスについて、代わりに ServiceDescriptor インスタンスで Add メソッドを直接呼び出します。 次に例を示します。

services.Add(ServiceDescriptor.Describe(
    serviceType: typeof(IConsole),
    implementationFactory: static _ => new DefaultConsole
    {
        IsEnabled = true
    },
    lifetime: ServiceLifetime.Singleton));

上記のコードは、IConsole サービスが ServiceCollection に登録された方法と同じです。 Add メソッドは、IConsole サービスを記述する ServiceDescriptor インスタンスを追加するために使用されます。 静的メソッド ServiceDescriptor.Describe は、さまざまな ServiceDescriptor コンストラクターにデリゲートします。 IGreetingService サービスの同等のコードを考えてみましょう。

services.Add(ServiceDescriptor.Describe(
    serviceType: typeof(IGreetingService),
    implementationType: typeof(DefaultGreetingService),
    lifetime: ServiceLifetime.Singleton));

上記のコードでは、IGreetingService サービスとそのサービスの種類、実装の種類、有効期間について説明します。 最後に、FarewellService サービスの同等のコードを検討します。

services.Add(ServiceDescriptor.Describe(
    serviceType: typeof(FarewellService),
    implementationType: typeof(FarewellService),
    lifetime: ServiceLifetime.Singleton));

上記のコードでは、具体的な FarewellService 型をサービス型と実装型の両方として記述します。 サービスはシングルトン サービスとして登録されます。

関連項目