玩转 .NET 依赖注入:Microsoft.Extensions.DependencyInjection 深度指南

玩转 .NET 依赖注入:Microsoft.Extensions.DependencyInjection 深度指南
前言在现代 .NET 开发自 .NET Core 起中依赖注入Dependency Injection简称 DI已经成为了框架的基石。微软官方提供了Microsoft.Extensions.DependencyInjection库它是一个轻量级、高性能的控制反转IoC容器。无论是开发 ASP.NET Core Web API、Worker Service 还是传统的 Console 控制台程序掌握这个库的用法都是每个 .NET 开发者迈向中高级的必经之路。本文将带你从零开始彻底掌握它的核心用法与最佳实践。️ 一、 核心概念与三个生命周期在学习如何使用之前必须先理解依赖注入的三种服务生命周期。理解它们能帮你避免绝大多数因生命周期错乱导致的 Bug比如在单例服务中注入瞬时服务。生命周期注册方法表现行为适用场景Transient瞬时AddTransient每次请求都会创建一个新的实例。轻量级、无状态的服务如工具类。Scoped作用域AddScoped在**同一个作用域Scope**内只创建一次。在 Web 开发中通常对应一次 HTTP 请求。带有状态的业务逻辑层Service、数据库上下文DbContext。Singleton单例AddSingleton整个应用程序运行期间只创建一次后续所有请求共用这一个实例。配置管理类、缓存管理类、公共线程池等。 二、 基础上手在控制台程序中使用要在非 ASP.NET Core 环境如控制台程序中使用它我们需要手动安装 Nuget 包Shelldotnet add package Microsoft.Extensions.DependencyInjection1. 完整代码示例以下代码展示了如何初始化容器、注册不同生命周期的服务并获取它们C#using System; using Microsoft.Extensions.DependencyInjection; namespace DiDemo { // 1. 定义接口与实现类 public interface IMessageService { void Send(string message); } public class EmailService : IMessageService { private readonly Guid _id Guid.NewGuid(); public void Send(string message) Console.WriteLine($[Email-{_id}] 发送: {message}); } class Program { static void Main(string[] args) { // 2. 构建服务容器集合 (ServiceCollection) var services new ServiceCollection(); // 3. 注册服务 services.AddTransientIMessageService, EmailService(); // 瞬时 // services.AddScopedIMessageService, EmailService(); // 作用域 // services.AddSingletonIMessageService, EmailService(); // 单例 // 4. 构建服务提供程序 (ServiceProvider) var serviceProvider services.BuildServiceProvider(); // 5. 从容器中解析并使用服务 var msgService1 serviceProvider.GetRequiredServiceIMessageService(); msgService1.Send(Hello World 1); var msgService2 serviceProvider.GetRequiredServiceIMessageService(); msgService2.Send(Hello World 2); } } }测试小贴士切换注释中的AddTransient、AddScoped和AddSingleton运行程序观察控制台输出的Guid是否相同就能直观感受到三种生命周期的区别 三、 高级进阶用法1. 构造函数注入Constructor InjectionDI 容器会自动寻找实现类的构造函数并自动注入它所依赖的其他服务。C#public interface IOrderService { void CreateOrder(); } public interface INotificationService { void Notify(); } public class OrderService : IOrderService { private readonly INotificationService _notificationService; // 容器会自动解析并传入 INotificationService 的实现 public OrderService(INotificationService notificationService) { _notificationService notificationService; } public void CreateOrder() { Console.WriteLine(订单创建成功); _notificationService.Notify(); } }2. 多个实现的注册与解析如果一个接口有多个实现类你可以全部注册它们C#services.AddTransientINotificationService, EmailNotification(); services.AddTransientINotificationService, SmsNotification();获取最后一个注册的实现使用GetRequiredServiceINotificationService()容器会返回最后注册的SmsNotification。获取所有实现使用GetServicesINotificationService()它会返回一个IEnumerableINotificationService集合包含所有已注册的实现。3. 使用 Scope作用域在控制台或后台任务中如果你需要手动隔离作用域模拟 Web 的一次请求可以使用CreateScope()C#using (var scope serviceProvider.CreateScope()) { // 在这个 using 块内所有 AddScoped 注册的服务只会实例化一次 var scopedService scope.ServiceProvider.GetRequiredServiceIMyScopedService(); } // 出了 using 块该 scope 内创建的所有服务实现了 IDisposable都会自动释放4. 尝试注册与替换为了防止重复注册或者在编写中间件、组件库时提供默认实现可以使用Microsoft.Extensions.DependencyInjection.Extensions命名空间下的扩展方法C#using Microsoft.Extensions.DependencyInjection.Extensions; // TryAddTransient只有当 IMessageService 没有被注册过时才会注册 EmailService services.TryAddTransientIMessageService, EmailService(); // TryAddEnumerable防止注册完全相同的实现 services.TryAddEnumerable(ServiceDescriptor.TransientIMessageService, EmailService()); // Replace替换已有的注册 services.Replace(ServiceDescriptor.SingletonIMessageService, NewCoolService());⚠️ 四、 常见踩坑点与避坑指南1. 什么是 Captive Dependency捕获依赖单例服务不能依赖生命周期比它短的服务例如一个Singleton服务在构造函数中注入了一个Scoped服务。因为Singleton只实例化一次导致那个Scoped服务也变成了单例这会引发并发冲突或数据错乱特别是注入DbContext时。解决方案在构建ServiceProvider时开启验证C#var serviceProvider services.BuildServiceProvider(validateScopes: true);在 .NET 6/7/8 的 ASP.NET Core 默认模板中开发环境Development已默认开启此项验证。2. GetService vs GetRequiredServiceGetServiceT()如果容器中找不到该服务返回null。GetRequiredServiceT()如果找不到服务直接抛出InvalidOperationException异常。最佳实践除非该服务是可选的Optional否则一律推荐使用GetRequiredServiceT()这样能尽早暴露配置错误。 五、 总结Microsoft.Extensions.DependencyInjection以其轻量、高效和开箱即用的特点成为了 .NET 生态中绝对的核心。牢记Transient、Scoped、Singleton的区别。编写代码时尽量依赖接口而非具体实现满足 DIP依赖倒置原则。注意生命周期陷阱善用validateScopes。掌握了这些不管是维护企业级老项目还是架构全新的微服务你都能在依赖管理上游刃有余。