.NET の依存関係の挿入

.NET では依存関係の挿入 (DI) ソフトウェア設計パターンがサポートされています。これは、クラスとその依存関係の間で制御の反転 (IoC) を実現するための手法です。 .NET での依存関係の挿入は、構成、ログ、オプション パターンと共に、フレームワークの組み込み部分です。

"依存関係" とは、他のオブジェクトが依存するオブジェクトのことです。 他のクラスが依存している、次の Write メソッドを備えた MessageWriter クラスを調べます。

public class MessageWriter
{
    public void Write(string message)
    {
        Console.WriteLine($"MessageWriter.Write(message: \"{message}\")");
    }
}

クラスは、MessageWriter クラスのインスタンスを作成して、その Write メソッドを使用することができます。 次の例で、MessageWriter クラスは Worker クラスの依存関係です。

public class Worker : BackgroundService
{
    private readonly MessageWriter _messageWriter = new();

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            _messageWriter.Write($"Worker running at: {DateTimeOffset.Now}");
            await Task.Delay(1_000, stoppingToken);
        }
    }
}

このクラスは MessageWriter クラスを作成し、これに直接依存しています。 ハードコーディングされた依存関係には、前の例のような問題があり、次の理由から回避する必要があります。

  • MessageWriter を別の実装で置き換えるには、Worker クラスを変更する必要があります。
  • MessageWriter が依存関係を含んでいる場合、これらは Worker クラスによって構成する必要があります。 複数のクラスが MessageWriter に依存している大規模なプロジェクトでは、構成コードがアプリ全体に分散するようになります。
  • このような実装では、単体テストを行うことが困難です。 アプリはモックまたはスタブの MessageWriter クラスを使用する必要がありますが、この方法では不可能です。

依存関係の挿入は、次の方法によってこれらの問題に対応します。

  • 依存関係の実装を抽象化するための、インターフェイスまたは基底クラスの使用。
  • サービス コンテナー内の依存関係の登録。 .NET には、組み込みのサービス コンテナー IServiceProvider が用意されています。 通常、サービスは、アプリの起動時に登録され、IServiceCollection に追加されます。 すべてのサービスが追加されたら、BuildServiceProvider を使用してサービス コンテナーを作成します。
  • サービスを使用するクラスのコンストラクターへの、サービスの "挿入"。 依存関係のインスタンスの作成、およびインスタンスが不要になったときの廃棄の役割を、フレームワークが担当します。

たとえば、IMessageWriter インターフェイスに Write メソッドを定義します。

namespace DependencyInjection.Example;

public interface IMessageWriter
{
    void Write(string message);
}

このインターフェイスは、具象型 MessageWriter によって実装されます。

namespace DependencyInjection.Example;

public class MessageWriter : IMessageWriter
{
    public void Write(string message)
    {
        Console.WriteLine($"MessageWriter.Write(message: \"{message}\")");
    }
}

このサンプル コードの場合、具象型 MessageWriter を使用して IMessageWriter サービスが登録されます。 AddSingleton メソッドでは、シングルトンの有効期間 (アプリの有効期間) でサービスを登録します。 サービスの有効期間については、この記事の後半で説明します。

using DependencyInjection.Example;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Services.AddHostedService<Worker>();
builder.Services.AddSingleton<IMessageWriter, MessageWriter>();

using IHost host = builder.Build();

host.Run();

前のコードでは、サンプル アプリで次の処理が行われます。

  • ホスト アプリ ビルダーのインスタンスを作成します。

  • 次を登録してサービスを構成します。

    • ホステッド サービスとしての Worker。 詳細については、「.NET の Worker サービス」を参照してください。
    • MessageWriter クラスの該当実装があるシングルトン サービスとしての IMessageWriter インターフェイス。
  • ホストをビルドして実行します。

ホストには、依存関係挿入サービス プロバイダーが含まれています。 また、Worker のインスタンスを自動的に作成し、対応する IMessageWriter 実装を引数として提供するために必要なその他すべての関連サービスが含まれています。

namespace DependencyInjection.Example;

public sealed class Worker(IMessageWriter messageWriter) : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            messageWriter.Write($"Worker running at: {DateTimeOffset.Now}");
            await Task.Delay(1_000, stoppingToken);
        }
    }
}

DI パターンを使用することにより、Worker サービスは次のようになります。

  • 具象型 MessageWriter は使用されず、実装される IMessageWriter インターフェイスのみが使用されます。 これにより、Worker サービスが使用する実装は、Worker サービスを変更することなく、簡単に変更できるようになります。
  • MessageWriter のインスタンスは作成できません。 このインスタンスは DI コンテナーによって作成されます。

組み込みのログ API を使用すると、IMessageWriter インターフェイスの実装を向上させることができます。

namespace DependencyInjection.Example;

public class LoggingMessageWriter(
    ILogger<LoggingMessageWriter> logger) : IMessageWriter
{
    public void Write(string message) =>
        logger.LogInformation("Info: {Msg}", message);
}

更新された AddSingleton メソッドでは、新しい IMessageWriter の実装が登録されます。

builder.Services.AddSingleton<IMessageWriter, LoggingMessageWriter>();

HostApplicationBuilder (builder) 型は、Microsoft.Extensions.Hosting NuGet パッケージの一部です。

LoggingMessageWriter は、コンストラクターで要求される ILogger<TCategoryName> によって異なります。 ILogger<TCategoryName> は、フレームワークで提供されるサービスです。

依存関係の挿入をチェーン形式で使用することはよくあります。 次に、要求されたそれぞれの依存関係が、それ自身の依存関係を要求します。 コンテナーによってグラフ内の依存関係が解決され、完全に解決されたサービスが返されます。 解決する必要がある依存関係の集合的なセットは、通常、"依存関係ツリー"、"依存関係グラフ"、または "オブジェクト グラフ" と呼ばれます。

コンテナーでは、(ジェネリック) オープン型を活用し、すべての (ジェネリック) 構築型を登録する必要をなくすことで、ILogger<TCategoryName> を解決します。

依存関係の挿入に関する用語では、サービスは次のようになります。

  • 通常、他のオブジェクト (IMessageWriter サービスなど) にサービスを提供するオブジェクトです。
  • Web サービスを使用する場合はありますが、サービスは Web サービスに関連付けられていません。

フレームワークは、堅牢な ログ システムを備えています。 前の例に示されている IMessageWriter の実装は、ログを実装するのではなく、基本的な DI を実演するために記述されています。 ほとんどのアプリでは、ロガーを記述する必要はありません。 次のコードは、既定のログを使用する方法を示しています。この場合、Worker をホステッド サービス AddHostedService として登録するだけで済みます。

public class Worker : BackgroundService
{
    private readonly ILogger<Worker> _logger;

    public Worker(ILogger<Worker> logger) =>
        _logger = logger;

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
            await Task.Delay(1_000, stoppingToken);
        }
    }
}

前のコードを使用すると、ログがフレームワークによって提供されるため、Program.cs を更新する必要はありません。

複数のコンストラクター検出ルール

型で複数のコンストラクターが定義されている場合、サービス プロバイダーにはどのコンストラクターを使うかを決定するためのロジックがあります。 型が DI 解決可能であるパラメーターを一番多く持つコンストラクターが選ばれます。 次の C# のサービス例を考えてみます。

public class ExampleService
{
    public ExampleService()
    {
    }

    public ExampleService(ILogger<ExampleService> logger)
    {
        // omitted for brevity
    }

    public ExampleService(FooService fooService, BarService barService)
    {
        // omitted for brevity
    }
}

上記のコードでは、ログ記録が追加されておりサービス プロバイダーから解決可能ですが、FooService 型と BarService 型は解決できないことを想定してください。 ILogger<ExampleService> パラメーターを持つコンストラクターが ExampleService インスタンスを解決するために使われます。 より多くのパラメーターが定義されたコンストラクターが存在しますが、FooService 型と BarService 型は DI 解決可能ではありません。

コンストラクターを検出するときにあいまいさがある場合は、例外がスローされます。 次の C# のサービス例を考えてみます。

public class ExampleService
{
    public ExampleService()
    {
    }

    public ExampleService(ILogger<ExampleService> logger)
    {
        // omitted for brevity
    }

    public ExampleService(IOptions<ExampleOptions> options)
    {
        // omitted for brevity
    }
}

警告

あいまいな DI 解決可能型パラメーターを持つ ExampleService コードでは、例外がスローされます。 これは行わないでください。これは "あいまいな DI 解決可能型" の意味を示すことを目的としたものです。

前の例では、3 つのコンストラクターがあります。 最初のコンストラクターにはパラメーターがなく、サービス プロバイダーからのサービスを必要としません。 ログ記録とオプションの両方が DI コンテナーに追加されており、DI 解決可能なサービスであることを想定してください。 DI コンテナーによって ExampleService 型の解決が試みられると、2 つのコンストラクターがあいまいであるため、例外がスローされます。

代わりに両方の DI 解決可能な型を受け入れるコンストラクターを定義することで、あいまいさを回避できます。

public class ExampleService
{
    public ExampleService()
    {
    }

    public ExampleService(
        ILogger<ExampleService> logger,
        IOptions<ExampleOptions> options)
    {
        // omitted for brevity
    }
}

拡張メソッドを使用したサービスのグループを登録する

Microsoft 拡張機能には、関連するサービスのグループを登録するための規則が使用されます。 規則は、単一の Add{GROUP_NAME} 拡張メソッドを使用して、フレームワーク機能に必要なすべてのサービスを登録するというものです。 たとえば、AddOptions 拡張メソッドにより、オプションの使用に必要なすべてのサービスが登録されます。

フレームワークが提供するサービス

使用可能なホストまたはアプリ ビルダー パターンのいずれかを利用すると、既定値が適用され、サービスがフレームワークによって登録されます。 最も一般的なホストおよびアプリ ビルダー パターンをいくつか考えてみましょう。

上記の API のいずれかでビルダーを作成したら、ホストの構成方法に応じて、フレームワークで定義されたサービスが IServiceCollection に登録されます。 .NET テンプレートをベースにしたアプリの場合、フレームワークで数百単位のサービスを登録できます。

次の表に、フレームワークによって登録されるサービスのごく一部を示します。

サービスの種類 有効期間
Microsoft.Extensions.DependencyInjection.IServiceScopeFactory シングルトン
IHostApplicationLifetime シングルトン
Microsoft.Extensions.Logging.ILogger<TCategoryName> シングルトン
Microsoft.Extensions.Logging.ILoggerFactory シングルトン
Microsoft.Extensions.ObjectPool.ObjectPoolProvider シングルトン
Microsoft.Extensions.Options.IConfigureOptions<TOptions> 一時的
Microsoft.Extensions.Options.IOptions<TOptions> シングルトン
System.Diagnostics.DiagnosticListener シングルトン
System.Diagnostics.DiagnosticSource シングルトン

サービスの有効期間

サービスは、次のいずれかの有効期間で構成できます。

次のセクションでは、前の有効期間について個別に説明します。 登録される各サービスの適切な有効期間を選択します。

一時的

有効期間が一時的なサービスは、サービス コンテナーから要求されるたびに作成されます。 "一時的なもの" としてサービスを登録するには、AddTransient を呼び出します。

要求を処理するアプリでは、一時的なサービスが要求の最後に破棄されます。 この有効期間では、サービスが解決され、毎回構築されるため、要求ごとの割り当てが発生します。 詳細については、「依存関係の挿入のガイドライン: 一時的なインスタンスと共有インスタンスのための IDisposable ガイダンス」を参照してください。

スコープ

Web アプリケーションの場合、スコープ付き有効期間は、クライアント要求 (接続) ごとにサービスが 1 回作成されることを示します。 AddScoped でスコープ付きサービスを登録します。

要求を処理するアプリでは、スコープ付きサービスは要求の最後で破棄されます。

Entity Framework Core を使用する場合、既定では AddDbContext 拡張メソッドによって、スコープ付き有効期間を持つ DbContext 型が登録されます。

Note

シングルトンからスコープ付きサービスを解決 "しないで" ください。また、たとえば一時的なサービスにより、間接的に解決しないようにご注意ください。 後続の要求を処理する際に、サービスが正しくない状態になる可能性があります。 次の場合は問題ありません。

  • スコープ付きまたは一時的なサービスからシングルトン サービスを解決する。
  • スコープ付きサービスを、別のスコープ付きまたは一時的なサービスから解決する。

既定では、開発環境で、より長い有効期間を持つ別のサービスからサービスを解決すると、例外がスローされます。 詳しくは、「スコープの検証」をご覧ください。

シングルトン

シングルトン有効期間サービスが作成されるのは、次のいずれかの場合です。

  • それらが初めて要求された場合。
  • 開発者によって、実装インスタンスがコンテナーに直接提供される場合。 このアプローチはほとんど必要ありません。

依存関係の挿入コンテナーから送信されるサービス実装の後続の要求すべてには、同じインスタンスが使用されます。 アプリをシングルトンで動作させる必要がある場合は、サービス コンテナーによるサービスの有効期間の管理を許可してください。 シングルトン デザイン パターンを実装したり、シングルトンを破棄するコードを提供したりしないでください。 コンテナーからサービスを解決したコードによって、サービスが破棄されることはありません。 型またはファクトリがシングルトンとして登録されている場合、コンテナーによってシングルトンが自動的に破棄されます。

シングルトン サービスを AddSingleton で登録します。 シングルトン サービスはスレッド セーフである必要があり、ほとんどの場合、ステートレス サービスで使用されます。

要求を処理するアプリでは、アプリのシャットダウン時に ServiceProvider が破棄されるとき、シングルトン サービスが破棄されます。 アプリがシャットダウンされるまでメモリは解放されないため、シングルトン サービスでのメモリ使用を考慮してください。

サービス登録メソッド

このフレームワークでは、特定のシナリオで役立つサービス登録拡張メソッドが提供されます。

方法 自動
object
破棄
複数
実装
引数を渡す
Add{LIFETIME}<{SERVICE}, {IMPLEMENTATION}>()

例:

services.AddSingleton<IMyDep, MyDep>();
はい はい いいえ
Add{LIFETIME}<{SERVICE}>(sp => new {IMPLEMENTATION})

例 :

services.AddSingleton<IMyDep>(sp => new MyDep());
services.AddSingleton<IMyDep>(sp => new MyDep(99));
はい イエス はい
Add{LIFETIME}<{IMPLEMENTATION}>()

例:

services.AddSingleton<MyDep>();
はい いいえ いいえ
AddSingleton<{SERVICE}>(new {IMPLEMENTATION})

例 :

services.AddSingleton<IMyDep>(new MyDep());
services.AddSingleton<IMyDep>(new MyDep(99));
いいえ イエス はい
AddSingleton(new {IMPLEMENTATION})

例 :

services.AddSingleton(new MyDep());
services.AddSingleton(new MyDep(99));
いいえ 番号 はい

型の廃棄の詳細については、「サービスの破棄」を参照してください。

実装型のみでサービスを登録することは、同じ実装とサービスの型でそのサービスを登録することと同じです。 明示的なサービス型を使用しないメソッドを使用してサービスの複数の実装を登録できないのは、このためです。 これらのメソッドでは、サービスの複数の "インスタンス" を登録できますが、すべて同じ "実装" 型になります。

上記のサービス登録メソッドのずれかを使用して、同じサービス型の複数のサービス インスタンスを登録できます。 次の例では、IMessageWriter をサービス型として使用して、AddSingleton を 2 回呼び出します。 2 回目の AddSingleton の呼び出しにより、IMessageWriter として解決された場合は前のものがオーバーライドされ、IEnumerable<IMessageWriter> を介して複数のサービスが解決された場合は前のものに追加されます。 IEnumerable<{SERVICE}> を介して解決された場合、サービスは登録された順に表示されます。

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

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Services.AddSingleton<IMessageWriter, ConsoleMessageWriter>();
builder.Services.AddSingleton<IMessageWriter, LoggingMessageWriter>();
builder.Services.AddSingleton<ExampleService>();

using IHost host = builder.Build();

_ = host.Services.GetService<ExampleService>();

await host.RunAsync();

上記のサンプル ソース コードを使用すると、IMessageWriter の 2 つの実装が登録されます。

using System.Diagnostics;

namespace ConsoleDI.IEnumerableExample;

public sealed class ExampleService
{
    public ExampleService(
        IMessageWriter messageWriter,
        IEnumerable<IMessageWriter> messageWriters)
    {
        Trace.Assert(messageWriter is LoggingMessageWriter);

        var dependencyArray = messageWriters.ToArray();
        Trace.Assert(dependencyArray[0] is ConsoleMessageWriter);
        Trace.Assert(dependencyArray[1] is LoggingMessageWriter);
    }
}

ExampleService により、2 つのコンストラクター パラメーター (1 つの IMessageWriterIEnumerable<IMessageWriter>) が定義されます。 1 つの IMessageWriter は登録された最後の実装です。一方、IEnumerable<IMessageWriter> は登録されたすべての実装を表します。

フレームワークには TryAdd{LIFETIME} 拡張メソッドも用意されており、実装がまだ登録されていない場合にのみ、サービスが登録されます。

次の例では、AddSingleton の呼び出しによって、IMessageWriter の実装として ConsoleMessageWriter が登録されます。 TryAddSingleton の呼び出しでは何も行われません。IMessageWriter には登録された実装が既に含まれているからです。

services.AddSingleton<IMessageWriter, ConsoleMessageWriter>();
services.TryAddSingleton<IMessageWriter, LoggingMessageWriter>();

TryAddSingleton は、既に追加されており、"試行" は失敗するため、効果はありません。 ExampleService により、以下がアサートされます。

public class ExampleService
{
    public ExampleService(
        IMessageWriter messageWriter,
        IEnumerable<IMessageWriter> messageWriters)
    {
        Trace.Assert(messageWriter is ConsoleMessageWriter);
        Trace.Assert(messageWriters.Single() is ConsoleMessageWriter);
    }
}

詳細については、以下を参照してください:

TryAddEnumerable(ServiceDescriptor) メソッドでは、"同じ型" の実装がまだ存在しない場合にのみサービスが登録されます。 複数のサービスは、IEnumerable<{SERVICE}> によって解決されます。 サービスを登録するときに、同じ型のいずれかがまだ追加されていない場合は、インスタンスを追加します。 ライブラリの作成者は、コンテナー内の実装の複数のコピーを登録しないようにするため TryAddEnumerable を使用します。

次の例では、TryAddEnumerable の最初の呼び出しで、IMessageWriter1 の実装として MessageWriter が登録されます。 2 番目の呼び出しでは IMessageWriter2MessageWriter が登録されます。 3 番目の呼び出しでは何も行われません。IMessageWriter1 には MessageWriter の登録済みの実装が既に含まれているからです。

public interface IMessageWriter1 { }
public interface IMessageWriter2 { }

public class MessageWriter : IMessageWriter1, IMessageWriter2
{
}

services.TryAddEnumerable(ServiceDescriptor.Singleton<IMessageWriter1, MessageWriter>());
services.TryAddEnumerable(ServiceDescriptor.Singleton<IMessageWriter2, MessageWriter>());
services.TryAddEnumerable(ServiceDescriptor.Singleton<IMessageWriter1, MessageWriter>());

サービスの登録は、通常、同じ種類の複数の実装を登録する場合を除き、順序に依存しません。

IServiceCollectionServiceDescriptor オブジェクトのコレクションです。 次の例は、ServiceDescriptor の作成と追加によってサービスを登録する方法を示しています。

string secretKey = Configuration["SecretKey"];
var descriptor = new ServiceDescriptor(
    typeof(IMessageWriter),
    _ => new DefaultMessageWriter(secretKey),
    ServiceLifetime.Transient);

services.Add(descriptor);

組み込みの Add{LIFETIME} メソッドでも同じ方法が使用されます。 たとえば、AddScoped のソース コードをご覧ください。

コンストラクターの挿入の動作

サービスは次を使用することによって解決できます。

  • IServiceProvider
  • ActivatorUtilities:
    • コンテナーに登録されていないオブジェクトが作成されます。
    • 一部のフレームワーク機能に使用されます。

コンストラクターは、依存関係の挿入によって提供されない引数を受け取ることができますが、引数は既定値を割り当てる必要があります。

IServiceProvider または ActivatorUtilities によってサービスを解決する場合、コンストラクターの挿入には "パブリック" コンストラクターが必要です。

ActivatorUtilities によってサービスを解決する場合、コンストラクターの挿入に必要なことは、該当するコンストラクターが 1 つだけ存在することです。 コンストラクターのオーバーロードはサポートされていますが、依存関係の挿入によってすべての引数を設定できるオーバーロードは 1 つしか存在できません。

スコープの検証

アプリが Development 環境で実行されていて、CreateApplicationBuilder を呼び出してホストを構築している場合、既定のサービス プロバイダーが以下を確認するチェックを実行します。

  • スコープ付きサービスが、ルート サービス プロバイダーによって解決されない。
  • スコープ付きサービスが、シングルトンに挿入されない。

BuildServiceProvider が呼び出されると、ルート サービス プロバイダーが作成されます。 ルート サービス プロバイダーの有効期間は、プロバイダーがアプリで開始されるとアプリの有効期間に対応し、アプリのシャットダウン時には破棄されます。

スコープ サービスは、それを作成したコンテナーによって破棄されます。 ルート コンテナーに作成されたスコープ付きサービスは、アプリのシャットダウン時に、ルート コンテナーによってのみ破棄されるため、サービスの有効期間は実質的にシングルトンに昇格されます。 BuildServiceProvider が呼び出されると、サービス スコープの検証がこれらの状況をキャッチします。

スコープのシナリオ

IServiceScopeFactory は常にシングルトンとして登録されますが、IServiceProvider は包含クラスの有効期間に基づいて変化する可能性があります。 たとえば、スコープからサービスを解決し、それらのサービスのいずれかが IServiceProvider 受け取ると、それは、スコープ付のインスタンスになります。

BackgroundService などの IHostedService の実装内でスコープ サービスを実現するには、コンストラクター インジェクションを介してサービスの依存関係を挿入 "しないでください"。 代わりに、IServiceScopeFactory を挿入し、スコープを作成し、そしてそのスコープから依存関係を解決して適切なサービス有効期間を使用します。

namespace WorkerScope.Example;

public sealed class Worker(
    ILogger<Worker> logger,
    IServiceScopeFactory serviceScopeFactory)
    : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            using (IServiceScope scope = serviceScopeFactory.CreateScope())
            {
                try
                {
                    logger.LogInformation(
                        "Starting scoped work, provider hash: {hash}.",
                        scope.ServiceProvider.GetHashCode());

                    var store = scope.ServiceProvider.GetRequiredService<IObjectStore>();
                    var next = await store.GetNextAsync();
                    logger.LogInformation("{next}", next);

                    var processor = scope.ServiceProvider.GetRequiredService<IObjectProcessor>();
                    await processor.ProcessAsync(next);
                    logger.LogInformation("Processing {name}.", next.Name);

                    var relay = scope.ServiceProvider.GetRequiredService<IObjectRelay>();
                    await relay.RelayAsync(next);
                    logger.LogInformation("Processed results have been relayed.");

                    var marked = await store.MarkAsync(next);
                    logger.LogInformation("Marked as processed: {next}", marked);
                }
                finally
                {
                    logger.LogInformation(
                        "Finished scoped work, provider hash: {hash}.{nl}",
                        scope.ServiceProvider.GetHashCode(), Environment.NewLine);
                }
            }
        }
    }
}

前のコードでは、アプリが実行されている間、バックグラウンド サービスは次のようになります。

  • IServiceScopeFactory に依存する。
  • 追加のサービスを解決するために IServiceScope を作成する。
  • 利用に向けてスコープ付のサービスを解決する。
  • オブジェクトを処理してから、それらを中継し、最後に処理済みとしてマークする。

サンプル ソース コードから、スコープ付きサービスの有効期間が IHostedService の実装にどのような恩恵をもたらすかを確認できます。

キー付きサービス

.NET 8 以降では、キーに基づくサービスの登録と検索もサポートされています。つまり、複数のサービスを別のキーで登録し、このキーを検索のために使用することが可能です。

たとえば、インターフェイス IMessageWriter の異なる実装 (MemoryMessageWriterQueueMessageWriter) がある場合を考えましょう。

これらのサービスは、次のようにパラメーターとしてキーをサポートするサービス登録メソッド (前述) のオーバーロードを使用して登録できます。

services.AddKeyedSingleton<IMessageWriter, MemoryMessageWriter>("memory");
services.AddKeyedSingleton<IMessageWriter, QueueMessageWriter>("queue");

keystring に限定されず、型が正しく Equals を実装する限り、任意の object とすることができます。

IMessageWriter を使用するクラスのコンストラクターで、次のように FromKeyedServicesAttribute を追加して解決するサービスのキーを指定します。

public class ExampleService
{
    public ExampleService(
        [FromKeyedServices("queue")] IMessageWriter writer)
    {
        // Omitted for brevity...
    }
}

関連項目