Transient, Scoped, Singleton 的區別
Transient 跟 Singleton 比較直觀,Transient 是「永遠創建新的實體 (instance)」,而 Singleton 剛好相反,是「只創建一次實體且永遠共享」;Scoped 則是以請求 (request) 為分界,同一個請求內只會創建一個共享的實體,但不同請求則會創建不同實體。可參考 Stack Overflow 上的說明:

Web API 範例
來看個範例會更好理解(完整原始碼可以從 GitHub 下載),首先創建一個 Web API 的範例專案
# 我電腦上的 dotnet 版本是 6.0
C:\Users\Jim>dotnet --version
6.0.202
C:\Users\Jim>dotnet new webapi -o ObjLifeCycle
The template "ASP.NET Core Web API" was created successfully.
...
接著建立新目錄/檔案 Interfaces/DemoClass.cs,並創造一些空的 interfaces 方便識別
# Interfaces/DemoClass.cs
namespace LifeCycleDemo.Interface;
public interface IScoped { }
public interface ISingleton { }
public interface ITransient { }
public class DemoClass : IScoped, ISingleton, ITransient { }
再建立一個新目錄/檔案 Services/DemoService.cs,在 constructor 中注入三種 interface 的實體,並且提供 GetServiceHashCode(),回傳其 data member 的 hash code (物件識別碼)
# Services/DemoService.cs
using LifeCycleDemo.Interface;
namespace LifeCycleDemo.Services;
public class DemoService
{
private readonly ITransient _transient;
private readonly IScoped _scoped;
private readonly ISingleton _singleton;
public DemoService(ITransient transient, IScoped scoped, ISingleton singleton)
{
_transient = transient;
_scoped = scoped;
_singleton = singleton;
}
public string GetServiceHashCode()
{
return $"Transient: {_transient.GetHashCode()}, Scoped:: {_scoped.GetHashCode()}, Singleton: {_singleton.GetHashCode()}";
}
}
接著在 Controllers 目錄下建一個新的 Controller DemoController.cs,在 constructor 中一樣注入三種 interface 的實體,再加上 DemoService 的實體;另外在 Get() 方法中,先取得其 DemoService 實體中所擁有的三個物件的 hash codes,再取得 DemoController 自己擁有的三個物件的 hash codes
# Controllers/DemoController.cs
using Microsoft.AspNetCore.Mvc;
using LifeCycleDemo.Services;
using LifeCycleDemo.Interface;
namespace LifeCycleDemo.Controllers;
[ApiController]
[Route("[controller]")]
public class DemoController : ControllerBase
{
private readonly ITransient _transient;
private readonly IScoped _scoped;
private readonly ISingleton _singleton;
public readonly DemoService _service;
public DemoController(ITransient transient, IScoped scoped, ISingleton singleton, DemoService service)
{
_transient = transient;
_scoped = scoped;
_singleton = singleton;
_service = service;
}
[HttpGet]
public ActionResult<IDictionary<string, string>> Get()
{
var serviceHash = _service.GetServiceHashCode();
var controllerHash = $"Transient: {_transient.GetHashCode()}, Scoped:: {_scoped.GetHashCode()}, Singleton: {_singleton.GetHashCode()}";
return new Dictionary<string, string> {
{"(First call) Obj Hash Codes in DemoService", serviceHash},
{"(Second call) Obj Hash Codes in DemoController", controllerHash}
};
}
}
最後在 Program.cs 中加入 DemoClass (三種不同生命週期)及 DemoService
# Program.cs
using LifeCycleDemo.Interface;
using LifeCycleDemo.Services;
...
// Add services to the container.
builder.Services.AddTransient<ITransient, DemoClass>();
builder.Services.AddScoped<IScoped, DemoClass>();
builder.Services.AddSingleton<ISingleton, DemoClass>();
builder.Services.AddScoped<DemoService, DemoService>();
準備完畢!到專案目錄下使用 dotnet CLI 啟動網站
C:\Users\Jim\ObjLifeCycle>dotnet run
Building...
info: Microsoft.Hosting.Lifetime[14]
Now listening on: <https://localhost:7197>
info: Microsoft.Hosting.Lifetime[14]
Now listening on: <http://localhost:5111>
...
連上 https://localhost:7197/demo,會看到如下畫面:

這裡發生的事情是:在初始化 DemoController 的時候,首先創造了三個 DemoClass 的實體 (分別為_transient, _scoped, _singleton 型別),接著創造 DemoService 實體,DemoService 自己在初始化過程中也會創造三個不同型別的 DemoClass 實體。這些實體到底是不是同一個?例如:DemoClass 的 _singleton 與 DemoService 的 _singleton 是同一個嗎?從物件的 hash code 就能判斷:
- _singleton 物件只會創建一次並且永遠共享,相同的 hash code 表示物件是同一個,且只有這一個
- _transient 物件每次都會創建新的,所以 hash code 永遠不同
- _scoped 物件是以請求 (request) 為分界,在同一次對於 DemoController 的 Get 請求中,_scoped 物件是共用同一個 (hash code 相同)
再發送一次請求可以看得更清楚。不要關閉分頁,再另開一個分頁,一樣連上 https://localhost:7197/demo,會看到如下畫面:

- _singleton 物件還是同一個 (hash code: 59481678 與上一個請求相同)
- _transient 物件永遠都不一樣
- 在這次請求中的 _scoped 物件是同一個 (hash code: 16839133),但與上一個請求的物件不同 (hash code: 9093001)
使用時機
該如何決定物件/服務的生命週期?
- 如果你不確定該使用那個,Scoped 通常是最佳選擇,在同一個請求內使用(共享)相同物件,可節省運算資源 ;不同請求使用不同物件,可避免 concurrency issue。DbContext 是一個使用 Scoped 的好例子
- Singleton 適合整個 App 共享的資源,如 logger, cache 等
- Transient 可能消耗最多運算資源,但對於避免 multithreading issue 也是最保險的作法
附上其他說明供參考

