跳转到内容

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:整个应用程序生命周期内共享一个实例。

graph LR A[客户端] -->|请求| B[依赖注入容器] B -->|提供| C[Transient 实例] B -->|提供| D[Scoped 实例] B -->|提供| E[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,会导致循环依赖。解决方案:

  • 重构代码,提取共用逻辑到第三个类
  • 使用方法注入替代构造函数注入

数学表示[编辑 | 编辑源代码]

依赖注入可以形式化表示为: Service=f(Dependency1,Dependency2,...,Dependencyn) 其中函数 f 通过注入的方式接收其依赖项。

总结[编辑 | 编辑源代码]

依赖注入是现代化 C# 开发的核心模式,它:

  • 通过解耦提高代码质量
  • 增强可测试性
  • 被 .NET Core 和 ASP.NET Core 原生支持

掌握依赖注入将显著提升你的应用程序架构能力。