C Sharp 依赖注入
外观
C#依赖注入[编辑 | 编辑源代码]
介绍[编辑 | 编辑源代码]
依赖注入(Dependency Injection,简称 DI)是 C# 中一种重要的设计模式,用于实现控制反转(Inversion of Control,IoC),以提升代码的可维护性、可测试性和松耦合性。依赖注入的核心思想是:一个类不应该直接创建它所依赖的对象,而是通过外部(如构造函数、属性或方法)传入这些依赖对象。
在 C# 中,依赖注入通常通过以下方式实现:
- 构造函数注入(最常用)
- 属性注入
- 方法注入
为什么使用依赖注入?[编辑 | 编辑源代码]
依赖注入的主要优势包括:
- 松耦合:减少类之间的直接依赖,使代码更容易修改和扩展。
- 可测试性:可以轻松替换依赖项(如模拟对象)进行单元测试。
- 可维护性:依赖关系清晰,便于管理和重构。
依赖注入的实现方式[编辑 | 编辑源代码]
1. 构造函数注入[编辑 | 编辑源代码]
构造函数注入是最推荐的方式,它在对象创建时传入依赖项。
public interface ILogger
{
void Log(string message);
}
public class ConsoleLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine(message);
}
}
public class UserService
{
private readonly ILogger _logger;
// 依赖通过构造函数注入
public UserService(ILogger logger)
{
_logger = logger;
}
public void AddUser(string username)
{
_logger.Log($"User {username} added.");
}
}
// 使用示例
ILogger logger = new ConsoleLogger();
var userService = new UserService(logger);
userService.AddUser("Alice");
输出:
User Alice added.
2. 属性注入[编辑 | 编辑源代码]
属性注入允许在对象创建后设置依赖项。
public class UserService
{
public ILogger Logger { get; set; }
public void AddUser(string username)
{
Logger?.Log($"User {username} added.");
}
}
// 使用示例
var userService = new UserService();
userService.Logger = new ConsoleLogger();
userService.AddUser("Bob");
3. 方法注入[编辑 | 编辑源代码]
方法注入通过方法参数传递依赖项。
public class UserService
{
public void AddUser(string username, ILogger logger)
{
logger.Log($"User {username} added.");
}
}
// 使用示例
var userService = new UserService();
userService.AddUser("Charlie", new ConsoleLogger());
依赖注入容器[编辑 | 编辑源代码]
在大型应用中,手动管理依赖关系可能变得复杂。C# 提供了依赖注入容器(如 .NET Core 内置的 `IServiceCollection`)来自动化这一过程。
示例:使用 .NET Core 的依赖注入[编辑 | 编辑源代码]
// Program.cs(.NET 6+)
using Microsoft.Extensions.DependencyInjection;
var services = new ServiceCollection();
// 注册服务
services.AddTransient<ILogger, ConsoleLogger>();
services.AddTransient<UserService>();
// 构建服务提供者
var serviceProvider = services.BuildServiceProvider();
// 解析服务
var userService = serviceProvider.GetRequiredService<UserService>();
userService.AddUser("Dave");
生命周期管理[编辑 | 编辑源代码]
依赖注入容器支持三种生命周期:
- Transient:每次请求都创建一个新实例。
- Scoped:在同一作用域内共享一个实例(如 HTTP 请求)。
- Singleton:整个应用程序生命周期内共享一个实例。
实际应用案例[编辑 | 编辑源代码]
假设我们正在开发一个电子商务系统,其中订单处理依赖于日志记录和支付服务:
public interface IPaymentService
{
bool ProcessPayment(decimal amount);
}
public class OrderProcessor
{
private readonly ILogger _logger;
private readonly IPaymentService _paymentService;
public OrderProcessor(ILogger logger, IPaymentService paymentService)
{
_logger = logger;
_paymentService = paymentService;
}
public bool ProcessOrder(Order order)
{
_logger.Log($"Processing order {order.Id}");
return _paymentService.ProcessPayment(order.Total);
}
}
// 注册服务
services.AddTransient<ILogger, FileLogger>();
services.AddTransient<IPaymentService, CreditCardPaymentService>();
services.AddTransient<OrderProcessor>();
常见问题[编辑 | 编辑源代码]
1. 依赖注入 vs 服务定位器[编辑 | 编辑源代码]
依赖注入(通过构造函数/属性)优于服务定位器模式(如直接调用 `serviceProvider.GetService`),因为它:
- 使依赖关系显式化
- 更易于测试
- 避免隐藏依赖
2. 循环依赖问题[编辑 | 编辑源代码]
如果 ClassA 依赖 ClassB,而 ClassB 又依赖 ClassA,会导致循环依赖。解决方案:
- 重构代码,提取共用逻辑到第三个类
- 使用方法注入替代构造函数注入
数学表示[编辑 | 编辑源代码]
依赖注入可以形式化表示为: 其中函数 通过注入的方式接收其依赖项。
总结[编辑 | 编辑源代码]
依赖注入是现代化 C# 开发的核心模式,它:
- 通过解耦提高代码质量
- 增强可测试性
- 被 .NET Core 和 ASP.NET Core 原生支持
掌握依赖注入将显著提升你的应用程序架构能力。