本文示例代码,均采用 .NET 6,具体的代码可以在这个仓库 Articles.DI 中获取。
前面的記事では、サービスの登録時に、services.AddSingleton<TService, TImplementation>()
の形式でサービスを登録することが一貫して使用されています。このメソッドの具体的な意味は何ですか?他の類似のメソッドはありますか?また、私たちには疑問があります。コンテナがサービスを構築する際のサービスのライフサイクルはどのようなものですか?サービスは 1 回申請されるたびに 1 回構築されますか?それともプログラム全体のライフサイクルで 1 回だけ構築されますか?各サービスに異なるライフサイクルをカスタマイズする場合、どのようにすればよいですか?以下では、サービスのライフサイクルについて一緒に探求していきます。
ライフサイクル#
コンテナに登録されるサービスのライフサイクルは、次の 3 つに分類されます。
ライフサイクル | 作成タイミング | 呼び出しメソッド |
---|---|---|
瞬時 / Transient | 各リクエストごとに新しいインスタンスが作成されます | AddTransient() |
スコープ / Scoped | 指定されたスコープ内で最初のリクエスト時にインスタンスが作成されます 同じスコープ内での繰り返しのリクエストでは、同じインスタンスが返されます | AddScoped() |
シングルトン / Singleton | プログラム全体のライフサイクルで 1 回だけ作成されます その後のすべてのリクエストでは、同じインスタンスが返されます | AddSingleton() |
実際のコードを参考にすると、依存性注入の 3 つのライフサイクルをより直感的に理解することができます。以下のコードは、Microsoft の.NET ドキュメントの対応する記事で見つけることができます - チュートリアル: .NET での依存関係の注入の使用。または、このリポジトリで直接ソースコードを確認することもできます。
// https://github.com/alva-lin/Articles.DI/tree/master/ConsoleApp1
// 01. インターフェースと実装クラスを宣言する
public interface IOperation
{
string OperationId { get; }
}
public interface ITransientOperation : IOperation {}
public interface IScopedOperation : IOperation {}
public interface ISingletonOperation : IOperation {}
public class DefaultOperation
: ITransientOperation, IScopedOperation, ISingletonOperation
{
// Guidを作成し、最後の4文字を取得する
public string OperationId { get; } = Guid.NewGuid().ToString()[^4..];
}
// 02. 上記のいくつかの Operationに依存するサービスを作成する
public class OperationLogger
{
private readonly ITransientOperation _transientOperation;
private readonly IScopedOperation _scopedOperation;
private readonly ISingletonOperation _singletonOperation;
public OperationLogger(ITransientOperation transientOperation,
IScopedOperation scopedOperation,
ISingletonOperation singletonOperation)
{
_transientOperation = transientOperation;
_scopedOperation = scopedOperation;
_singletonOperation = singletonOperation;
}
public void LogOperations(string scope)
{
LogOperation(_transientOperation, scope, "Always different");
LogOperation(_scopedOperation, scope, "Changes only with scope");
LogOperation(_singletonOperation, scope, "Always the same");
}
private static void LogOperation<T>(T operation, string scope, string message)
where T : IOperation
{
Console.WriteLine($"{scope}: {typeof(T).Name,-19} [{operation.OperationId} {message,-23}]");
}
}
// 03. サービスを登録し、プログラムを起動する
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using IHost host = Host.CreateDefaultBuilder(args)
.ConfigureServices(services =>
{
// 3つのOperationをそれぞれのライフサイクルに登録する
services.AddTransient<OperationLogger>()
.AddTransient<ITransientOperation, DefaultOperation>()
.AddScoped<IScopedOperation, DefaultOperation>()
.AddSingleton<ISingletonOperation, DefaultOperation>();
})
.Build();
ExemplifyScoping(host.Services, "Scope 1");
ExemplifyScoping(host.Services, "Scope 2");
await host.RunAsync();
static void ExemplifyScoping(IServiceProvider services, string scope)
{
using IServiceScope serviceScope = services.CreateScope();
IServiceProvider provider = serviceScope.ServiceProvider;
var logger = provider.GetRequiredService<OperationLogger>();
logger.LogOperations($"{scope}: Call 1 ...");
Console.WriteLine();
logger = provider.GetRequiredService<OperationLogger>();
logger.LogOperations($"{scope}: Call 2 ...");
Console.WriteLine();
}
上記のコードを実行すると、次の結果が得られます。
Scope 1: Call 1 ...: ITransientOperation [b672 Always different ]
Scope 1: Call 1 ...: IScopedOperation [afd8 Changes only with scope]
Scope 1: Call 1 ...: ISingletonOperation [21b3 Always the same ]
Scope 1: Call 2 ...: ITransientOperation [b6fc Always different ]
Scope 1: Call 2 ...: IScopedOperation [afd8 Changes only with scope]
Scope 1: Call 2 ...: ISingletonOperation [21b3 Always the same ]
Scope 2: Call 1 ...: ITransientOperation [ef31 Always different ]
Scope 2: Call 1 ...: IScopedOperation [46d1 Changes only with scope]
Scope 2: Call 1 ...: ISingletonOperation [21b3 Always the same ]
Scope 2: Call 2 ...: ITransientOperation [9864 Always different ]
Scope 2: Call 2 ...: IScopedOperation [46d1 Changes only with scope]
Scope 2: Call 2 ...: ISingletonOperation [21b3 Always the same ]
プログラムの出力ログを比較すると、各ITransientOperation
の出力が異なることがわかります。一方、IScopedOperation
は同じスコープ内では同じ内容を出力し、異なるスコープ間では異なる内容を出力します。最後のISingletonOperation
は、常に同じ内容を出力します。これらの出力結果は、前述の表の内容とも一致しています。