このチュートリアルでは、 .NET で依存関係挿入 (DI) を使用する方法について説明します。 Microsoft Extensions では、DI はサービスを追加し、IServiceCollectionで構成することによって管理されます。 IHost インターフェイスは、登録されているすべてのサービスのコンテナーとして機能するIServiceProvider インスタンスを公開します。
このチュートリアルでは、以下の内容を学習します。
- 依存関係の挿入を使用する .NET コンソール アプリを作成する
- 汎用ホストをビルドして構成する
- 複数のインターフェイスと対応する実装を記述する
- DI にサービスの有効期間とスコープを使用する
[前提条件]
- .NET Core 3.1 SDK 以降。
- 新しい .NET アプリケーションの作成と NuGet パッケージのインストールに関する知識。
新しいコンソール アプリケーションを作成する
dotnet new コマンドまたは IDE の新しいプロジェクト ウィザードを使用して、ConsoleDI.Example という名前の新しい .NET コンソール アプリケーションを作成します。 Microsoft.Extensions.Hosting NuGet パッケージをプロジェクトに追加します。
新しいコンソール アプリ プロジェクト ファイルは次のようになります。
<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.Configuration.Binder" Version="9.0.5" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.5" />
</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
を明示的に実装します。 たとえば、IExampleTransientService
は、ServiceLifetime.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
として定義され、対応するインターフェイスを実装します。 internal
やsealed
である必要はありません。ただし、実装の種類を外部コンシューマーにリークしないように、実装をinternal
として扱うのが一般的です。 さらに、各型は拡張されないため、 sealed
としてマークされます。 たとえば、 ExampleSingletonService
は IExampleSingletonService
を実装します。
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
は、前述の各サービス インターフェイス(IExampleTransientService
、IExampleScopedService
、および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 サービスは常に同じです。新しいインスタンスは 1 回だけ作成されます。
こちらも参照ください
- 依存関係の挿入のガイドライン
- .NET での依存関係の挿入の基本について理解する
- ASP.NET Core における依存関係の注入
.NET