EKsumic's Blog

let today = new Beginning();

Click the left button to use the catalog.

OR

解釋「生命周期」的概念,以及 Singleton、Scoped 和 Transient 的含義

1. 什麼是生命周期?

在 Blazor 或 ASP.NET Core 中,服務的生命周期 指的是 一個服務實例的存活時間,也就是說:

  • 從服務創建到釋放期間,它可以被重複使用多少次,或者被誰共享?

當我們註冊一個服務時(例如:builder.Services.AddScoped<MyService>()),我們需要告訴系統:

  1. 這個服務應該存活多久
  2. 應該在哪些情況下重複使用

2. 三種常見的生命周期

(1)Singleton(單例)

  • 定義全應用 只創建一個實例,所有地方都使用同一個實例。
  • 什麼時候用?
    • 當服務是無狀態的(比如全局設置、日誌記錄器)。
    • 或者服務需要在整個應用中共享狀態。

範例:

builder.Services.AddSingleton<MyService>();
  • 效果:不論在哪個組件中注入 MyService,它們都會使用同一個實例。
  • 應用場景:配置信息、全局的狀態管理。

(2)Scoped(範圍)

  • 定義
    • Blazor Server 模式中,Scoped 表示服務的實例在 同一個用戶的連接 中是共享的。
    • Blazor WebAssembly 模式中,Scoped 的行為與 Singleton 一樣,整個應用中只有一個實例。
  • 什麼時候用?
    • 當需要針對每個用戶或每個連接保持獨立的狀態(如用戶的登錄信息、會話數據)。

範例:

builder.Services.AddScoped<MyService>();
  • 效果
    • 對於同一個用戶,該用戶的所有組件會共享同一個服務實例。
    • 不同用戶之間,服務的實例是分開的。

(3)Transient(瞬態)

  • 定義每次請求 都會創建一個新的服務實例,完全不共享。
  • 什麼時候用?
    • 當服務只執行短期操作,並且不需要共享狀態時。
    • 避免狀態污染,例如處理瞬時數據或計算操作。

範例:

builder.Services.AddTransient<MyService>();
  • 效果
    • 每次注入 MyService,系統都會創建一個新的實例。
    • 不同組件、不同請求使用的都是全新的一個 MyService

3. 具體例子解釋

假設我們有一個服務 AppState,用於保存當前用戶的名字:

服務代碼:AppState

public class AppState
{
    public string CurrentUser { get; set; } = "Guest";
}

情境 1:Singleton

Program.cs 中註冊:

builder.Services.AddSingleton<AppState>();

使用效果:

  • 所有用戶 共用同一個 AppState 實例。
  • 如果某個用戶更改了 AppState.CurrentUser,所有用戶都會看到相同的更改。

範例代碼:

組件 1(改變狀態):

@inject AppState AppState

<p>Current User: @AppState.CurrentUser</p>
<button @onclick="ChangeUser">Set User as Alice</button>

@code {
    private void ChangeUser()
    {
        AppState.CurrentUser = "Alice";
    }
}

組件 2(讀取狀態):

@inject AppState AppState

<p>Current User: @AppState.CurrentUser</p>

結果:改變在所有地方生效


情境 2:Scoped

Program.cs 中註冊:

builder.Services.AddScoped<AppState>();

使用效果(Blazor Server):

  • 每個用戶AppState 實例是獨立的。
  • 同一個用戶的多個組件共享同一個實例。

範例代碼:

組件 1(改變狀態):

@inject AppState AppState

<p>Current User: @AppState.CurrentUser</p>
<button @onclick="ChangeUser">Set User as Alice</button>

@code {
    private void ChangeUser()
    {
        AppState.CurrentUser = "Alice";
    }
}

組件 2(讀取狀態):

@inject AppState AppState

<p>Current User: @AppState.CurrentUser</p>

結果:

  • 同一個用戶看到的名字會改變。
  • 不同用戶之間的名字彼此獨立。

情境 3:Transient

Program.cs 中註冊:

builder.Services.AddTransient<AppState>();

 

使用效果:

  • 每次注入 都是全新的實例。
  • 不同的組件和請求,無法共享狀態。

範例代碼:

組件 1(改變狀態):

@inject AppState AppState

<p>Current User: @AppState.CurrentUser</p>
<button @onclick="ChangeUser">Set User as Alice</button>

@code {
    private void ChangeUser()
    {
        AppState.CurrentUser = "Alice";
    }
}


組件 2(讀取狀態):

@inject AppState AppState

<p>Current User: @AppState.CurrentUser</p>

結果:

  • 即使在組件 1 設置了 AppState.CurrentUser,組件 2 中仍然顯示默認值 Guest

4. 小結

生命周期 作用範圍 適用場景
Singleton 全應用共享一個實例。 配置信息、全局狀態、日誌記錄。
Scoped 每個用戶的連接範圍內共享一個實例(Blazor Server);等同於 Singleton(Blazor WebAssembly)。 用戶相關的狀態管理,如登錄會話。
Transient 每次請求創建新的實例,不共享。 短期操作,如輕量級計算。
  • 核心區別 是服務的作用範圍和共享行為。
  • 根據應用場景選擇合適的生命周期,可以提高代碼的性能和可維護性。

5. 補充

當你在 .razor 組件裡直接 聲明並實例化一個類 時,這個類的生命周期是 與該組件的生命周期一致。也就是說,這個類的實例在組件的 每次加載或重新渲染 時會被重新創建,並在組件被銷毀時一起銷毀

This article was last edited at 2025-01-16 15:23:15

* *