在 ASP.NET Core 開發中,DI (dependency injection) 是最核心的設計模式之一,AddTransient(), AddScoped(), AddSingleton() 三種生命週期的差異能達到的目標截然不同,本文希望釐清當中的目的以及誤解。

核心概念

三種生命週期的定義

生命週期 實例建立時機 使用場景
Transient 每次注入時都建立新實例 輕量級、無狀態的服務
Scoped 每個 HTTP Request 內共用同一實例 DbContext、需要在單一請求內保持狀態的服務
Singleton 整個應用程式生命週期只建立一次 設定檔、快取、共用資源

常見誤解

Scoped 以及 Transient 常會被人誤解

Scoped:每一次的 Request 是共用同一個 Service Instance
Transient:每一次的 Request,每次使用 Service 都會建立一個的 Instance

範例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
// ========================================
// IExampleService.cs
// ========================================
public interface IExampleService
{
Guid InstanceId { get; }
}

// ========================================
// ExampleService.cs
// ========================================
public class ExampleService : IExampleService
{
public Guid InstanceId { get; } = Guid.NewGuid();

public ExampleService()
{
// 每次建立新實例時,產生新的 GUID
Console.WriteLine($"ExampleService 建立: {InstanceId}");
}
}

// ========================================
// LifecycleController.cs
// ========================================
[ApiController]
[Route("api/[controller]")]
public class LifecycleController : ControllerBase
{
private readonly IExampleService _service1;
private readonly IExampleService _service2;

public LifecycleController(
IExampleService service1,
IExampleService service2)
{
_service1 = service1;
_service2 = service2;
}

[HttpGet("test")]
public IActionResult Test(
[FromServices] IExampleService service3)
{
return Ok(new
{
Service1 = _service1.InstanceId,
Service2 = _service2.InstanceId,
Service3 = service3.InstanceId,
AllSame = _service1.InstanceId == _service2.InstanceId
&& _service2.InstanceId == service3.InstanceId
});
}
}

// ========================================
// Program.cs
// ========================================
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
var app = builder.Build();
app.MapControllers();
app.Run();

builder.Services.AddTransient<IExampleService, ExampleService>();
/// {"constructor_Service1":{"id":"cb49da66-e8a0-48af-89a9-0c845a0d50a0"},
/// "constructor_Service2":{"id":"0ff0d6e2-92d4-4128-85fc-158e64834ac1"},
/// "method_Service3":{"id":"7f613727-e0dd-416d-95cc-ab5b78495274"}}

builder.Services.AddScope<IExampleService, ExampleService>();
/// {"constructor_Service1":{"id":"042ad90c-97bc-4284-83f4-93e7c6b8dd7f"},
/// "constructor_Service2":{"id":"042ad90c-97bc-4284-83f4-93e7c6b8dd7f"},
/// "method_Service3":{"id":"042ad90c-97bc-4284-83f4-93e7c6b8dd7f"}}