【C#】依存性注入メソッドのライフサイクル  - AddTransient, AddScoped, AddSingleton

# はじめに
C#での依存性注入（DI）では、3つの依存性注入メソッドのライフサイクル（`AddTransient`、`AddScoped`、`AddSingleton`）が使用できます。

https://learn.microsoft.com/ja-jp/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-8.0&preserve-view=true#lifetime-and-registration-options

ざっくりまとめると以下のようになっています。
| ライフタイム     | インスタンスの生成タイミング                      |
|-----------------|-----------------------------------------------|
| `AddTransient`  | 要求ごとに新しいインスタンスを生成                |
| `AddScoped`     | リクエストごとに1つのインスタンスを生成            | 
| `AddSingleton`  | アプリケーション全体で1つのインスタンスを生成        |

それぞれ依存性注入するのは変わりませんが、オブジェクトの生成タイミングが違うので実際に比較します。

# 環境
.NET 8

# お試しコード
`AddTransient`、`AddScoped`、`AddSingleton`を以下のコードで切り替えながら`serve`エンドポイントを叩いてみて試してみます。

```c#:Program.cs
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）を返すことで、インスタンスがどのように再利用されているかを確認します。

```c#:IService.cs
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 でそれぞれ別のインスタンスが生成されます。
```sh
❯ 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 で同じインスタンスが使われます。
```sh
❯ 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つのインスタンスだけが生成されます。リクエストが異なっても同じインスタンスが再利用されます。
```sh
❯ 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`はメモリ効率化やスコープを気にせず使えるという点で利点があります（この点については別の記事で掘り下げようと思います）。
それぞれのライフサイクルを理解し、適切に使い分けることが重要です。