はじめに
C#での依存性注入(DI)では、3つの依存性注入メソッドのライフサイクル(AddTransient
、AddScoped
、AddSingleton
)が使用できます。
ざっくりまとめると以下のようになっています。
ライフタイム | インスタンスの生成タイミング |
---|---|
AddTransient |
要求ごとに新しいインスタンスを生成 |
AddScoped |
リクエストごとに1つのインスタンスを生成 |
AddSingleton |
アプリケーション全体で1つのインスタンスを生成 |
それぞれ依存性注入するのは変わりませんが、オブジェクトの生成タイミングが違うので実際に比較します。
環境
.NET 8
お試しコード
AddTransient
、AddScoped
、AddSingleton
を以下のコードで切り替えながらserve
エンドポイントを叩いてみて試してみます。
using DILifeCycle;
using Microsoft.AspNetCore.Mvc;
var builder = WebApplication.CreateBuilder(args);
// 依存性注入コンテナへのサービス登録
// AddTransient、AddScoped、AddSingleton を切り替えて試す
builder.Services.AddTransient<IService, ServiceA>();
// builder.Services.AddScoped<IService, ServiceA>();
// builder.Services.AddSingleton<IService, ServiceA>();
var app = builder.Build();
// ルーティングの設定
app.MapGet("/serve", (IService service1, [FromServices] IService service2) =>
{
var id1 = service1.Serve();
var id2 = service2.Serve();
return Results.Ok(new { Message = "Service Called", id1, id2 });
});
// アプリケーションの実行
app.Run();
このコードでは、2つのサービスインスタンス(service1とservice2)を解決し、UUID(InstanceId)を返すことで、インスタンスがどのように再利用されているかを確認します。
public interface IService
{
Guid Serve();
}
public class ServiceA : IService
{
public Guid InstanceId { get; private set; }
public ServiceA()
{
// インスタンスIDを設定
InstanceId = Guid.NewGuid();
Console.WriteLine($"ServiceA instance created with ID: {InstanceId}");
}
public Guid Serve()
{
Console.WriteLine($"ServiceA is serving. Instance ID: {InstanceId}");
return InstanceId;
}
}
このクラスは、インスタンスが生成されるたびに新しいUUIDが割り振られ、Serve() メソッドを呼び出すことでそのUUIDを返します。
AddTransient
AddTransientは、要求ごとに新しいインスタンスを生成します。つまり、service1 と service2 でそれぞれ別のインスタンスが生成されます。
❯ curl http://localhost:5076/serve | jq .
{
"message": "Service Called",
"id1": "ae19b175-dab2-45fd-beee-1fb34656f1ba",
"id2": "b09c9024-9772-4ebf-ba34-aed3cf216a40"
}
❯ curl http://localhost:5076/serve | jq .
{
"message": "Service Called",
"id1": "defcdb91-e4d2-43f8-a1ff-73cd8679dd5d",
"id2": "e932d7fd-071a-4eda-842b-c1c6cdd797e1"
}
常にIDが変わっているので、オブジェクトが要求ごとに生成されていることがわかります。
都度newしている感じですね。
AddScoped
AddScopedでは、リクエストごとに1つのインスタンスが生成されます。リクエスト内であれば、service1 と service2 で同じインスタンスが使われます。
❯ curl http://localhost:5076/serve | jq .
{
"message": "Service Called",
"id1": "8ad88baf-f438-4d7e-8ea0-06de051e352c",
"id2": "8ad88baf-f438-4d7e-8ea0-06de051e352c"
}
❯ curl http://localhost:5076/serve | jq .
{
"message": "Service Called",
"id1": "e05ff550-4646-47fe-a487-a913b150a27c",
"id2": "e05ff550-4646-47fe-a487-a913b150a27c"
}
リクエスト毎にIDが変わっているので、オブジェクトがリクエスト毎に生成されていることがわかります。
AddSingleton
AddSingletonは、アプリケーション全体で1つのインスタンスだけが生成されます。リクエストが異なっても同じインスタンスが再利用されます。
❯ curl http://localhost:5076/serve | jq .
{
"message": "Service Called",
"id1": "42f8ecf5-7b86-46a1-b3d9-aae8e17c92e8",
"id2": "42f8ecf5-7b86-46a1-b3d9-aae8e17c92e8"
}
❯ curl http://localhost:5076/serve | jq .
{
"message": "Service Called",
"id1": "42f8ecf5-7b86-46a1-b3d9-aae8e17c92e8",
"id2": "42f8ecf5-7b86-46a1-b3d9-aae8e17c92e8"
}
IDが常に普遍なので実行時に生成されていることがわかります。
まとめ
AddTransient
は、都度新しいインスタンスが生成され、状態を持たない軽量なサービスに向いています。
一方、AddScoped
はリクエストごとに同じインスタンスを使用するため、1つのリクエスト内でサービスを共有する必要がある場合に便利です。
AddSingleton
は、アプリケーション全体で1つのインスタンスを再利用するため、状態を保持する必要があるサービスに適しています。
脳死でAddSingleton
やAddScoped
を使いがちですが、AddTransient
はメモリ効率化やスコープを気にせず使えるという点で利点があります(この点については別の記事で掘り下げようと思います)。
それぞれのライフサイクルを理解し、適切に使い分けることが重要です。