.NET 依賴注入深入詳解
原為鏈接:http://www.rzrgm.cn/ysmc/p/18796964
.NET 依賴注入深入詳解
依賴注入(Dependency Injection, DI)是.NET Core .NET 5/6/7/8/9/10+中最重要的設計模式之一,下面我將從多個維度詳細解釋它的工作原理和使用方法。
一、核心概念解析
1. 什么是依賴?
當一個類A需要類B才能正常工作時,我們就說類A"依賴"于類B。例如:
public class OrderService { private readonly ILogger _logger; // OrderService依賴于ILogger public OrderService(ILogger logger) { _logger = logger; } }
2. 傳統方式的問題
沒有DI時,我們可能會這樣寫:
public class OrderService { private readonly FileLogger _logger = new FileLogger(); // 直接創建具體實現 // ... }
這種方式的缺點:
-
緊耦合:OrderService直接依賴FileLogger
-
難以測試:無法輕松替換為測試用的Logger
-
難以修改:如果要改用DatabaseLogger,需要修改OrderService代碼
二、.NET DI 容器詳解
1. 服務注冊方式
在Program.cs/Startup.cs中有三種主要注冊方式:
// 1. 注冊具體類型 services.AddTransient<EmailService>(); // 2. 注冊接口-實現映射 services.AddScoped<IEmailService, SmtpEmailService>(); // 3. 注冊現有實例 var logger = new FileLogger(); services.AddSingleton<ILogger>(logger);
2. 生命周期詳解
| 生命周期 | 描述 | 適用場景 | 示例 |
|---|---|---|---|
| Transient | 每次請求都創建新實例 | 輕量級、無狀態服務 | 工具類、DTO映射 |
| Scoped | 同一請求內共享實例 | 需要請求上下文的服務 | DbContext、用戶會話 |
| Singleton | 整個應用生命周期一個實例 | 全局共享資源 | 配置、緩存、日志 |
3. 高級注冊技巧
// 注冊多個實現 services.AddTransient<IMessageService, SmsService>(); services.AddTransient<IMessageService, EmailService>(); // 命名注冊 services.AddTransient<IMessageService>("SMS", typeof(SmsService)); // 泛型注冊 services.AddScoped(typeof(IRepository<>), typeof(Repository<>)); // 委托工廠 services.AddTransient<IService>(sp => new Service(sp.GetRequiredService<IOtherService>()));
三、注入方式大全
1. 構造函數注入(最推薦)
public class ProductController { private readonly IProductService _service; public ProductController(IProductService service) { _service = service; // 由DI容器自動注入 } }
2. 方法注入
public class ReportGenerator { public void Generate(IReportFormatter formatter) { // 使用方法參數注入 } }
3. 屬性注入(不推薦但某些場景需要,如blazor)
public class NotificationService { [Inject] // 需要特定屬性標記 public ILogger Logger { get; set; } }
4. 從容器直接解析(應避免,但有時必要)
var service = serviceProvider.GetRequiredService<IMyService>();
四、實際應用場景
1. 分層架構中的DI
// 基礎設施層 services.AddDbContext<AppDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("Default"))); // 應用層 services.AddScoped<IOrderService, OrderService>(); // 領域層 services.AddTransient<IDomainEventDispatcher, DomainEventDispatcher>(); // 表現層 services.AddControllersWithViews();
2. 選項模式(Options Pattern)
// 注冊配置 services.Configure<EmailSettings>(Configuration.GetSection("EmailSettings")); // 注入使用 public class EmailService { private readonly EmailSettings _settings; public EmailService(IOptions<EmailSettings> options) { _settings = options.Value; } }
3. 日志集成
public class ProductService { private readonly ILogger<ProductService> _logger; public ProductService(ILogger<ProductService> logger) { _logger = logger; // 自動注入日志系統 } }
五、高級主題
1. 第三方容器集成
// Autofac示例 builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory()) .ConfigureContainer<ContainerBuilder>(builder => { builder.RegisterModule(new MyAutofacModule()); });
2. 裝飾器模式
// 原始服務 services.AddScoped<IDataService, DataService>(); // 裝飾器 services.Decorate<IDataService, CachingDataService>();
3. 驗證服務注冊
// 檢查所有服務是否已注冊 var provider = services.BuildServiceProvider(); foreach (var service in services) { provider.GetRequiredService(service.ServiceType); // 會拋出異常如果未注冊 }
六、最佳實踐
-
構造函數保持簡單:只注入必要的依賴
-
避免服務定位器模式:不要濫用GetService
-
注意生命周期:避免Scoped服務被Singleton服務引用
-
使用接口:盡量依賴抽象而非具體實現
-
避免過度注入:如一個類注入超過5個服務,考慮重構
七、常見問題解決
循環依賴問題
// 錯誤示例 class A { public A(B b) {} } class B { public B(A a) {} } // 解決方案: // 1. 重構設計,提取公共邏輯到第三個類 // 2. 使用屬性注入或方法注入替代構造函數注入
多實現選擇
// 注冊多個實現 services.AddTransient<IMessageService, EmailService>(); services.AddKeyedTransient<IMessageService, SmsService>("SMS"); // 解析所有實現 var services = provider.GetServices<IMessageService>(); // 命名解析 var smsService = provider.GetRequiredKeyedService<IMessageService>("SMS");
感謝各位大佬的觀看!
本文來自博客園,作者:一事冇誠,轉載請注明原文鏈接:http://www.rzrgm.cn/ysmc/p/18796964

浙公網安備 33010602011771號