.NET 依赖注入深入详解

打印 上一主题 下一主题

主题 1798|帖子 1798|积分 5394

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
原为链接: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。比方:
  1. public class OrderService
  2. {
  3.     private readonly ILogger _logger; // OrderService依赖于ILogger
  4.    
  5.     public OrderService(ILogger logger)
  6.     {
  7.         _logger = logger;
  8.     }
  9. }
复制代码
2. 传统方式的题目

没有DI时,我们可能会如许写:
  1. public class OrderService
  2. {
  3.     private readonly FileLogger _logger = new FileLogger(); // 直接创建具体实现
  4.    
  5.     // ...
  6. }
复制代码
这种方式的缺点:

  • 紧耦合:OrderService直接依赖FileLogger
  • 难以测试:无法轻松替换为测试用的Logger
  • 难以修改:如果要改用DatabaseLogger,必要修改OrderService代码
二、.NET DI 容器详解

1. 服务注册方式

在Program.cs/Startup.cs中有三种主要注册方式:
  1. // 1. 注册具体类型
  2. services.AddTransient<EmailService>();
  3. // 2. 注册接口-实现映射
  4. services.AddScoped<IEmailService, SmtpEmailService>();
  5. // 3. 注册现有实例
  6. var logger = new FileLogger();
  7. services.AddSingleton<ILogger>(logger);
复制代码
2. 生命周期详解

生命周期形貌适用场景示例Transient每次请求都创建新实例轻量级、无状态服务工具类、DTO映射Scoped同一请求内共享实例必要请求上下文的服务DbContext、用户会话Singleton整个应用生命周期一个实例全局共享资源配置、缓存、日记 

 

 

3. 高级注册技巧
  1. // 注册多个实现
  2. services.AddTransient<IMessageService, SmsService>();
  3. services.AddTransient<IMessageService, EmailService>();
  4. // 命名注册
  5. services.AddTransient<IMessageService>("SMS", typeof(SmsService));
  6. // 泛型注册
  7. services.AddScoped(typeof(IRepository<>), typeof(Repository<>));
  8. // 委托工厂
  9. services.AddTransient<IService>(sp =>
  10.     new Service(sp.GetRequiredService<IOtherService>()));
复制代码
三、注入方式大全

1. 构造函数注入(最保举)
  1. public class ProductController
  2. {
  3.     private readonly IProductService _service;
  4.    
  5.     public ProductController(IProductService service)
  6.     {
  7.         _service = service; // 由DI容器自动注入
  8.     }
  9. }
复制代码
2. 方法注入
  1. public class ReportGenerator
  2. {
  3.     public void Generate(IReportFormatter formatter)
  4.     {
  5.         // 使用方法参数注入
  6.     }
  7. }
复制代码
3. 属性注入(不保举但某些场景必要,如blazor)
  1. public class NotificationService
  2. {
  3.     [Inject] // 需要特定属性标记
  4.     public ILogger Logger { get; set; }
  5. }
复制代码
4. 从容器直接剖析(应避免,但有时必要)
  1. var service = serviceProvider.GetRequiredService<IMyService>();
复制代码
四、实际应用场景

1. 分层架构中的DI
  1. // 基础设施层
  2. services.AddDbContext<AppDbContext>(options =>
  3.     options.UseSqlServer(Configuration.GetConnectionString("Default")));
  4. // 应用层
  5. services.AddScoped<IOrderService, OrderService>();
  6. // 领域层
  7. services.AddTransient<IDomainEventDispatcher, DomainEventDispatcher>();
  8. // 表现层
  9. services.AddControllersWithViews();
复制代码
2. 选项模式(Options Pattern)
  1. // 注册配置
  2. services.Configure<EmailSettings>(Configuration.GetSection("EmailSettings"));
  3. // 注入使用
  4. public class EmailService
  5. {
  6.     private readonly EmailSettings _settings;
  7.    
  8.     public EmailService(IOptions<EmailSettings> options)
  9.     {
  10.         _settings = options.Value;
  11.     }
  12. }
复制代码
3. 日记集成
  1. public class ProductService
  2. {
  3.     private readonly ILogger<ProductService> _logger;
  4.    
  5.     public ProductService(ILogger<ProductService> logger)
  6.     {
  7.         _logger = logger; // 自动注入日志系统
  8.     }
  9. }
复制代码
五、高级主题

1. 第三方容器集成
  1. // Autofac示例
  2. builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory())
  3.     .ConfigureContainer<ContainerBuilder>(builder =>
  4.     {
  5.         builder.RegisterModule(new MyAutofacModule());
  6.     });
复制代码
2. 装饰器模式
  1. // 原始服务
  2. services.AddScoped<IDataService, DataService>();
  3. // 装饰器
  4. services.Decorate<IDataService, CachingDataService>();
复制代码
3. 验证服务注册
  1. // 检查所有服务是否已注册
  2. var provider = services.BuildServiceProvider();
  3. foreach (var service in services)
  4. {
  5.     provider.GetRequiredService(service.ServiceType); // 会抛出异常如果未注册
  6. }
复制代码
六、最佳实践


  • 构造函数保持简朴:只注入必要的依赖
  • 避免服务定位器模式:不要滥用GetService
  • 注意生命周期:避免Scoped服务被Singleton服务引用
  • 使用接口:尽量依赖抽象而非具体实现
  • 避免过分注入:如一个类注入超过5个服务,考虑重构
七、常见题目解决

循环依赖题目
  1. // 错误示例
  2. class A { public A(B b) {} }
  3. class B { public B(A a) {} }
  4. // 解决方案:
  5. // 1. 重构设计,提取公共逻辑到第三个类
  6. // 2. 使用属性注入或方法注入替代构造函数注入
复制代码
多实现选择
  1. // 注册多个实现
  2. services.AddTransient<IMessageService, EmailService>();
  3. services.AddKeyedTransient<IMessageService, SmsService>("SMS");
  4. // 解析所有实现
  5. var services = provider.GetServices<IMessageService>();
  6. // 命名解析
  7. var smsService = provider.GetRequiredKeyedService<IMessageService>("SMS");
复制代码
感谢各位大佬的观看!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
继续阅读请点击广告
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

千千梦丶琪

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表