作者的評價#

本章的基調與前兩章截然不同。作者對 Microsoft.Extensions.DependencyInjection(以下簡稱 MS.DI)給出了直接且批判性的評價:

「它的功能如此有限,不適合任何有一定規模、實踐鬆耦合的應用程式。」

這個強烈的立場貫穿全章,作者逐項說明 MS.DI 的設計意圖與實際限制。

基本架構#

MS.DI 同樣採用兩階段設計

// 階段一:配置 ServiceCollection
var services = new ServiceCollection();
services.AddTransient<IProductService, ProductService>();

// 階段二:建構 ServiceProvider
var provider = services.BuildServiceProvider(
    validateScopes: true);  // 建議開啟 Scope 驗證

Figure 15.1: Microsoft.Extensions.DependencyInjection 的使用模式:先配置,再解析

註冊方式#

MS.DI 以方法名稱直接表達 Lifetime,三種註冊方法對應三種生命週期:

services.AddTransient<IService, ServiceImpl>();   // 每次新建
services.AddSingleton<IService, ServiceImpl>();   // 單例
services.AddScoped<IService, ServiceImpl>();      // 每 Scope 一個

當同一個介面有多個註冊時,MS.DI 採用 Last Registration Wins 語意——後註冊的會靜默覆蓋先前的,不會產生任何錯誤或警告。這在大型專案中極容易導致難以追蹤的 bug。

Scope 管理#

與 Autofac 類似,MS.DI 要求從 IServiceScope 解析物件:

using (var scope = provider.CreateScope())
{
    var service = scope.ServiceProvider.GetRequiredService<IProductService>();
}

永遠從 IServiceScope 解析,而非直接從 Root ServiceProvider。 直接從 Root 解析會導致 Scoped 物件無法被正確釋放。

Figure 15.3: MS.DI 的 Scope 作為可共享元件的容器

主要限制#

本章的重點在於詳述 MS.DI 的多項重大限制:

無 Decorator 支援#

MS.DI 沒有內建的 Decorator 註冊機制。要實現 Decorator Pattern,必須使用 Lambda 手動包裝:

services.AddTransient<IRepository, SqlRepository>();

// 痛苦的 Decorator 手動包裝
services.AddTransient<IRepository>(provider =>
{
    var inner = new SqlRepository(
        provider.GetRequiredService<IDbContext>());
    return new CachingRepository(inner);
});

這種做法不僅冗長,而且失去了 Auto-Wiring 的好處——必須手動解析 Decorator 內層的所有依賴。

無 Composite 支援#

同樣缺乏內建的 Composite Pattern 支援,需要類似的 Lambda workaround。

無 Auto-Registration / Assembly 掃描#

MS.DI 不提供任何 Assembly 掃描或慣例式註冊機制。每一個型別映射都必須手動逐一註冊。這意味著——

  • 失去了 DI Container 相對於 Pure DI 最大的優勢
  • 大型應用程式的 Composition Root 會變得冗長且重複

無驗證與診斷#

MS.DI 沒有 Verify() 之類的驗證方法:

  • 無法在啟動時檢測註冊是否完整
  • 無法偵測 Captive Dependencies(如 Singleton 持有 Scoped 物件)
  • 這類錯誤只能在執行期被使用者觸發時才會浮現

無條件式註冊#

不支援根據 context 或 predicate 條件性地註冊不同的實作。

設計初衷#

理解這些限制後,需要認識 MS.DI 的設計初衷

  • 它是為框架和函式庫開發者設計的最小公約數容器
  • 目標是提供一個所有 .NET 框架(ASP.NET Core、EF Core 等)能共同依賴的最低限度 DI 抽象
  • 並非為應用程式開發者提供完整功能的 DI 解決方案

如果你的應用程式只需要基本的 Constructor Injection 和簡單的 Lifetime 管理,MS.DI 可能就夠用了。但一旦你需要 Decorator、Composite、Auto-Registration 或驗證機制,就應該考慮使用 Autofac 或 Simple Injector。

與其他容器的對比#

功能AutofacSimple InjectorMS.DI
Decorator 支援內建內建(含條件式)
Composite 支援內建內建
Auto-Registration支援支援
驗證 / 診斷有限Verify() 完整支援
泛型型別約束有(編譯期安全)
Captive Dependency 偵測
MS.DI 重點回顧
  • 作者明確認為 MS.DI 不適合有一定規模的鬆耦合應用程式
  • Last Registration Wins 語意可能導致靜默覆蓋的隱性 bug
  • 缺乏 Decorator、Composite、Auto-Registration、驗證等關鍵功能
  • 設計定位是框架開發者的最小公約數容器,非完整 DI 解決方案
  • 如果需要進階功能,應改用 Autofac 或 Simple Injector