跳到主要内容

C# 依赖注入

依赖注入(Dependency Injection, DI)是一种设计模式,用于实现控制反转(Inversion of Control, IoC)。它允许我们将对象的创建和依赖关系的管理从代码中分离出来,从而实现更松耦合、更易于测试和维护的代码。

什么是依赖注入?

在传统的编程模式中,一个类通常会直接创建它所依赖的对象。例如:

csharp
public class OrderService
{
private readonly IOrderRepository _orderRepository;

public OrderService()
{
_orderRepository = new OrderRepository();
}

public void ProcessOrder(Order order)
{
_orderRepository.Save(order);
}
}

在这个例子中,OrderService 直接创建了 OrderRepository 的实例。这种方式的问题在于,OrderServiceOrderRepository 紧密耦合,难以进行单元测试或替换依赖的实现。

依赖注入通过将依赖对象的创建和管理交给外部容器来解决这个问题。我们可以通过构造函数、属性或方法参数来“注入”依赖对象。

依赖注入的三种方式

1. 构造函数注入

构造函数注入是最常见的依赖注入方式。它通过构造函数将依赖对象传递给类。

csharp
public class OrderService
{
private readonly IOrderRepository _orderRepository;

public OrderService(IOrderRepository orderRepository)
{
_orderRepository = orderRepository;
}

public void ProcessOrder(Order order)
{
_orderRepository.Save(order);
}
}

在这个例子中,OrderService 不再负责创建 OrderRepository,而是通过构造函数接收它。这使得 OrderService 更加灵活,易于测试。

2. 属性注入

属性注入通过类的属性来注入依赖对象。

csharp
public class OrderService
{
public IOrderRepository OrderRepository { get; set; }

public void ProcessOrder(Order order)
{
OrderRepository.Save(order);
}
}

这种方式在某些场景下很有用,但它不如构造函数注入常见,因为它可能导致依赖对象在类实例化后未被正确设置。

3. 方法注入

方法注入通过方法的参数来注入依赖对象。

csharp
public class OrderService
{
public void ProcessOrder(Order order, IOrderRepository orderRepository)
{
orderRepository.Save(order);
}
}

这种方式适用于依赖对象只在特定方法中使用的情况。

依赖注入容器

依赖注入容器(DI Container)是一个用于管理依赖关系的工具。它可以自动创建和注入依赖对象,简化了依赖注入的实现。

在C#中,常用的依赖注入容器包括:

  • Microsoft.Extensions.DependencyInjection:ASP.NET Core 默认的依赖注入容器。
  • Autofac:一个功能强大的第三方依赖注入容器。
  • Unity:另一个流行的依赖注入容器。

使用 Microsoft.Extensions.DependencyInjection

以下是一个使用 Microsoft.Extensions.DependencyInjection 的示例:

csharp
using Microsoft.Extensions.DependencyInjection;

var services = new ServiceCollection();

// 注册服务
services.AddTransient<IOrderRepository, OrderRepository>();
services.AddTransient<OrderService>();

// 构建服务提供者
var serviceProvider = services.BuildServiceProvider();

// 获取 OrderService 实例
var orderService = serviceProvider.GetService<OrderService>();

// 使用 OrderService
orderService.ProcessOrder(new Order());

在这个例子中,我们首先注册了 IOrderRepositoryOrderService,然后通过 ServiceProvider 获取 OrderService 的实例。依赖注入容器会自动处理 OrderService 的依赖关系。

实际应用场景

依赖注入在以下场景中非常有用:

  1. 单元测试:通过依赖注入,我们可以轻松地替换依赖对象为模拟对象(Mock),从而更容易进行单元测试。
  2. 模块化设计:依赖注入使得代码模块化,每个模块只关注自己的职责,降低了代码的耦合度。
  3. 可扩展性:通过依赖注入,我们可以轻松地替换或扩展依赖对象的实现,而无需修改现有代码。

示例:单元测试

假设我们有一个 OrderService,它依赖于 IOrderRepository。我们可以使用依赖注入来注入一个模拟的 IOrderRepository,从而测试 OrderService 的行为。

csharp
public class OrderServiceTests
{
[Fact]
public void ProcessOrder_ShouldSaveOrder()
{
// 创建模拟的 IOrderRepository
var mockRepository = new Mock<IOrderRepository>();

// 创建 OrderService 实例,并注入模拟的 IOrderRepository
var orderService = new OrderService(mockRepository.Object);

// 调用 ProcessOrder 方法
orderService.ProcessOrder(new Order());

// 验证 Save 方法是否被调用
mockRepository.Verify(repo => repo.Save(It.IsAny<Order>()), Times.Once);
}
}

在这个单元测试中,我们使用 Moq 库创建了一个模拟的 IOrderRepository,并通过构造函数注入到 OrderService 中。然后我们验证了 Save 方法是否被正确调用。

总结

依赖注入是一种强大的设计模式,它可以帮助我们编写更松耦合、更易于测试和维护的代码。通过使用依赖注入容器,我们可以进一步简化依赖关系的管理。

在实际项目中,依赖注入广泛应用于ASP.NET Core、WPF、Xamarin等框架中。掌握依赖注入不仅有助于提高代码质量,还能提升开发效率。

附加资源与练习

通过不断实践和深入学习,你将能够熟练运用依赖注入来构建高质量的C#应用程序。