addScoped、addTransient、addSingletonの違いを整理

※当サイトは、アフィリエイト広告を利用しています
C#(ASP.NET)
スポンサーリンク

この記事では、ASP.NETの依存性注入(Dependency Injection, DI)でよく使われる「addScoped、addTransient、addSingleton」についての記事です。

新しい職場がC#で初めてみたものだったので自分の頭の整理含めて記事にまとめようと思います。

違いがよく分かるように実際にコードをつかって紹介していきます。

この記事を読むメリット

  • addScoped、addTransient、addSingletonの違いが理解できる
  • addScoped、addTransient、addSingletonそれぞれの使い所がわかる
スポンサーリンク

DI(依存性の注入)について

まずは前提として、DIについて軽く整理します。

DI(Dependency Injection)は依存性の注入とよく言われますが、言葉だけだと何のことかわかりません。

自分もきちんと理解できているかと言われれば怪しいですが、「クラスの中で別のクラスを自分で new して使うのではなく、外部からインスタンスを注入してもらう形でオブジェクトを扱う」ということだと思っています。

public class HomeController : Controller
{
    private readonly IMyService _service;

    public HomeController(IMyService service)
    {
        _service = service;
    }
}

インターフェース経由でインスタンスを扱うことで色々とメリットがあるようです。

メリットは以下のようなものがあります。

  • クラス間の依存関係を少なくできる
  • テストがしやすい
  • 再利用性・拡張性が高まる

ここで、「インターフェースに対してどのクラスをどのライフサイクルで使うか」を登録する必要があります。

この登録の際に、ライフサイクルの設定をするのが「addScoped、addTransient、addSingleton」になります。

それぞれ「インスタンスがどのタイミングで生成され、いつ破棄されるか」決めることができます。

スポンサーリンク

addScoped、addTransient、addSingletonの違い

どのように使われるか整理できましたので実際にそれぞれの違いを見ていきます。

表にすると、以下のようになります。

ライフサイクル生成タイミング特徴
addTransient依存注入されるたびに毎回生成される軽量で状態を持たない処理に最適
addScopedリクエストごとに1回生成されるリクエストの状態を保持して使いたいケース向き
addSingletonアプリ起動時に1回だけインスタンスが生成されるアプリ全体で共有されるインスタンスに最適

エンジニアにおすすめ書籍

エンジニアになりたて、これから勉強を深めていきたいという方におすすめの書籍はこちら!

実際のコードで違いを確認

言葉だけだとイメージしづらいので実際のコードで確認してみたいと思います。

まずはサンプルのServiceクラスを作成します。


namespace backend.Service 
{
  public interface IGuidService
  {
      string GetGuid();
  }

  public class GuidService : IGuidService
  {
      private readonly string _guid;

      public GuidService()
      {
          _guid = Guid.NewGuid().ToString();
          Console.WriteLine($"New GuidService Created: {_guid}");
      }

      public string GetGuid()
      {
          return _guid;
      }
  }

}

このGuidServiceは、「Guid.NewGuid()」でGUIDを生成して、それを返すだけの単純なクラスです。

ちなみに、Guidは、「Globally Unique Identifier(グローバル一意識別子)」の略で、グローバルに一意のランダム文字列です。

続いてContoller側で先ほどのServiceを呼び出します。

using backend.Services;
using Microsoft.AspNetCore.Mvc;

namespace backend.Controllers
{
  [ApiController]
  [Route("api/guid")]
  public class TestController : ControllerBase
  {
      private readonly IGuidService _service1;
      private readonly IGuidService _service2;

      public TestController(IGuidService service1, IGuidService service2)
      {
          _service1 = service1;
          _service2 = service2;
      }

      [HttpGet]
      public IActionResult Get()
      {
          return Ok(new
          {
              First = _service1.GetGuid(),
              Second = _service2.GetGuid()
          });
      }
  }
}

最後にDIの登録を行います。

ここの登録の仕方で挙動がどう変わるか確認するため、以下のようにしています。

builder.Services.AddTransient<IGuidService, GuidService>();
// builder.Services.AddScoped<IGuidService, GuidService>();
// builder.Services.AddSingleton<IGuidService, GuidService>();

addScoped、addTransient、addSingletonでそれぞれ登録するコードを書き、コメントアウトで1つずつ挙動を確認してみます。

AddTransientの挙動

まずはAddTransientの挙動を確認します。

{
    "first": "bbb54ca1-12a5-4c0c-bfe1-a7b8ee5e515e",
    "second": "77cee4a0-4399-4c9a-89d0-045feaf19884"
}

結果はこうなりました。

idが異なるので、別の新しいインスタンスが生成されていることがわかります。

addScopedの挙動

まずはaddScopedの挙動を確認します。

{
    "first": "1d369988-e8f9-48c6-9dc4-97de05d44ad2",
    "second": "1d369988-e8f9-48c6-9dc4-97de05d44ad2"
}

結果はこうなりました。

同じリクエスト内での処理になるので、同じインスタンスが使用されます。

別のリクエストで同じ処理を実行するapiを作成しました。

[HttpGet("ver2")]
public IActionResult GetVer2()
{
    return Ok(new
    {
        First = _service1.GetGuid(),
        Second = _service2.GetGuid()
    });
}

これで確認すると、以下の結果が返ってきます。

{
    "first": "ba535e06-1035-465e-a3a9-4e71a94ff436",
    "second": "ba535e06-1035-465e-a3a9-4e71a94ff436"
}

リクエストが違うと違うインスタンスが使われることが分かります。

addSingletonの挙動

最後はaddSingletonの挙動を確認します。

{
    "first": "d359fe1f-a318-4cba-bf0b-18a203f0ac2c",
    "second": "d359fe1f-a318-4cba-bf0b-18a203f0ac2c"
}

addScopedと同じように、同じインスタンスを使われています。

別リクエストでの結果は以下になります。

{
    "first": "d359fe1f-a318-4cba-bf0b-18a203f0ac2c",
    "second": "d359fe1f-a318-4cba-bf0b-18a203f0ac2c"
}

別リクエストでも同じインスタンスが使われています。

addScoped、addTransient、addSingletonの違いまとめ

改めて違いを整理してみると以下のようになります。

ライフサイクル生成タイミング特徴同一リクエスト内リクエストまたぎ
addTransient依存注入のたびに毎回生成軽量で状態を持たない処理に最適別インスタンス別インスタンス
addScopedリクエストごとに生成リクエストの状態を保持して使いたいケース向き同じインスタンス別インスタンス
addSingletonアプリ起動時に1回だけ生成アプリ全体で共有されるインスタンスに最適同じインスタンス同じインスタンス

実際にコードを書いて確かめてみると違いを整理しやすかったです。

理解が曖昧だったりした場合はこのようにコードで確認してみるのがおすすめです。

自分もこの機会に整理ができてよかったです。

タイトルとURLをコピーしました