.NET 依赖注入深入详解

.NET 依赖注入深入详解

原为链接: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();

// 2. 注册接口-实现映射

services.AddScoped();

// 3. 注册现有实例

var logger = new FileLogger();

services.AddSingleton(logger);

2. 生命周期详解

生命周期描述适用场景示例

Transient

每次请求都创建新实例

轻量级、无状态服务

工具类、DTO映射

Scoped

同一请求内共享实例

需要请求上下文的服务

DbContext、用户会话

Singleton

整个应用生命周期一个实例

全局共享资源

配置、缓存、日志

3. 高级注册技巧

// 注册多个实现

services.AddTransient();

services.AddTransient();

// 命名注册

services.AddTransient("SMS", typeof(SmsService));

// 泛型注册

services.AddScoped(typeof(IRepository<>), typeof(Repository<>));

// 委托工厂

services.AddTransient(sp =>

new Service(sp.GetRequiredService()));

三、注入方式大全

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();

四、实际应用场景

1. 分层架构中的DI

// 基础设施层

services.AddDbContext(options =>

options.UseSqlServer(Configuration.GetConnectionString("Default")));

// 应用层

services.AddScoped();

// 领域层

services.AddTransient();

// 表现层

services.AddControllersWithViews();

2. 选项模式(Options Pattern)

// 注册配置

services.Configure(Configuration.GetSection("EmailSettings"));

// 注入使用

public class EmailService

{

private readonly EmailSettings _settings;

public EmailService(IOptions options)

{

_settings = options.Value;

}

}

3. 日志集成

public class ProductService

{

private readonly ILogger _logger;

public ProductService(ILogger logger)

{

_logger = logger; // 自动注入日志系统

}

}

五、高级主题

1. 第三方容器集成

// Autofac示例

builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory())

.ConfigureContainer(builder =>

{

builder.RegisterModule(new MyAutofacModule());

});

2. 装饰器模式

// 原始服务

services.AddScoped();

// 装饰器

services.Decorate();

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();

services.AddKeyedTransient("SMS");

// 解析所有实现

var services = provider.GetServices();

// 命名解析

var smsService = provider.GetRequiredKeyedService("SMS");

感谢各位大佬的观看!

相关推荐

为什么伤口结痂时会痒?
GBT36507-2018

为什么伤口结痂时会痒?

📅 08-31 👁️ 6311
华为在国外市场的发展现状(探寻华为在国外市场的成功之道)
365网站取款不给怎么办

华为在国外市场的发展现状(探寻华为在国外市场的成功之道)

📅 08-09 👁️ 9202
秒白条多少芝麻分才能开通?真相来了,别再被误导了!
水獭为什么不怕食人鱼?天生就爱吃鱼连凯门鳄也不怕
夏天开得最灿烂的10种花,看看哪种最惊艳你!
《寻仙手游中黄鼠狼的技能与属性解析》
bet28365体育

《寻仙手游中黄鼠狼的技能与属性解析》

📅 07-27 👁️ 415