原为链接:https://www.cnblogs.com/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");
感谢各位大佬的观看!
来源链接:https://www.cnblogs.com/ysmc/p/18796964
© 版权声明
本站所有资源来自于网络,仅供学习与参考,请勿用于商业用途,否则产生的一切后果将由您(转载者)自己承担!
如有侵犯您的版权,请及时联系3500663466#qq.com(#换@),我们将第一时间删除本站数据。
如有侵犯您的版权,请及时联系3500663466#qq.com(#换@),我们将第一时间删除本站数据。
THE END
暂无评论内容