Contents

NET Core 依賴注入 AddSingleton AddScoped AddTransient 方法與差異

我認為依賴注入是一個很重要觀念,現在每個程式語言都會有這個東西。這個東西學會不管接觸什麼程式語言都是一樣的東西,建議還是學會,學完觀念在配置上 DI 會非常方便。

簡單例子介紹三種差異

下圖參考: Understanding AddTransient Vs AddScoped Vs AddSingleton In ASP.NET Core

https://i.imgur.com/3N42eyJ.png

這邊我們可到上面圖片有兩個 Request ,這邊經過兩個 Class 實例 (Instance),建構子會注入相關物件,我們可以觀察上面 ID 知道物件生命週期。這邊箭頭顏色表示該物件生命週期。

  1. Transient 每經過物件注入,這邊可以看到他的生命週期在每個物件實例 (Instance) 都會重新 new 物件。通常會用在資料表 Repository(這邊不是資料庫連線)。
  2. Scoped 每一次 Request 都會建立實例,經過每個 Class 會繼續沿用,直到 Request 結束會釋放記憶體。通常會用在資料庫連線。
  3. Singleton 程式起來就會實例,任何物件做注入會繼續沿用之前建立物件。

我的 CheatSheet

  • AddSingleton 程式建立就換建立物件,生命週期直到程式結束。

  • AddScoped : HTTP Request 生命週期。

  • AddTransient: 每個範圍注入都會建立實例。

一般類別註冊

一般使用就能在容器注冊一個物件。

1
services.AddSingleton<TestService>();

但通常我們都是一個 Interface 對一個 Class。

1
services.AddSingleton<ITestService, TestService>();

Singleton 自建物件方法

在學 Spring Boot 在有一個 注入 Config.java 會設定相關 DI 物件,Spring Boot 都是自己 new Class去做注冊。但上面我們沒有 new 出來,但是 Net Core 一樣也能做到,這個優點就是能對 物件設定屬性。

Warning
2024-04-25 我發現 Spring 和 .Net Core DI 單立模式不太一樣,Spring 啟動時候就會 new 出來,但 .Net Core 是在需要的時候才會 new 出來。所以建構子不要做檢查設定,有需要的話要寫在 Program.cs,這邊要注意。
1
2
3
4
var service = new TestService(){
    test = "test"
};
builder.Services.AddSingleton<ITestService>(service);
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
    public interface ITestService
    {
        string Test();
    }

    public class TestService : ITestService
    {
        public string TestString { get; set; }
        public string Test() {
            return $"string{TestString}";
        }
    }

泛型注入

1
services.AddTransient(typeof(IRepository<,>), typeof(RepositoryBase<,>));

一個 IRepository 可以RepositoryBase這樣做泛型注入。但這樣實作上可以達到自動注入,但實務上寫 Service 也不會這樣做。通常interfaceclass 去做,內建沒有自動注入,但透過第三方套件可以做到這件事。

Info
你可能會很好奇typeof(IRepository<,>)怎可以這樣使用,其實這是 C# 映射泛型類別可以做的事情。詳細可以看
如何:使用反映檢視和執行個體化泛型類型 - .NET Framework | Microsoft Learn

正確注入方式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public class MyClass
{
    private readonly IOptionsMonitor<MyOptions> _optionsMonitor;

    public MyClass(IOptionsMonitor<MyOptions> optionsMonitor)
    {
        _optionsMonitor = optionsMonitor;
    }

    public void MyMethod()
    {
        var option = _optionsMonitor.CurrentValue.Option;

        ...
    }
}

不建議用GetService()

  • 另一個要避免的服務定位器變化是插入在執行階段解析相依性的處理站。 這兩種做法都會混用控制反轉策略。

  • 避免以靜態方式存取 HttpContext (例如 IHttpContextAccessor.HttpContext)。

DI 是靜態/全域物件存取模式的「替代」選項。 如果您將 DI 與靜態物件存取混合,則可能無法實現 DI 的優點。

設定一個 Interface 指定不同 Class 情況

之前寫 Spring Boot,定義一個 interface 指定到 class 只能有一個,不然 Sprinig Boot 不知道要用哪一個,程式啟動就會抱錯。.Net Core我目前沒看到文章在討論這個,不過我簡單做個實驗,發現會後蓋前。

Spring Boot 啟動 DI 衝突需要解決處理。
https://i.imgur.com/h8ewacr.png

.Net Core 會後續執行順序去做

.NET Core 中的相依性插入 | Microsoft Docs

自動載入也是後面吃到哪個 class 為主。但這邊可以用List注入去做多個Service載入。有空在筆記。

https://i.imgur.com/S7JmmPF.png

這邊會吃到到 TestService(不是2)的class。

這邊還要注意,假如你用Interface對應Class去註冊的話,在Controller或其他地方取出來的時候,使用Class參數引進的話,不會抓到任何東西,會跑出InvalidOperationException,這邊跟 Spring Boot不太一樣。Laravel是不是也這樣,我就不太清楚。(那時候我還不熟DI,只是覺得很神奇)