深入理解 .NET 中的依赖注入(DI):从原理到实战

深入理解 .NET 中的依赖注入(DI):从原理到实战

依赖注入(Dependency Injection,简称 DI)是现代 .NET 开发中不可或缺的一部分。无论是 ASP.NET Core Web API、MVC、Worker Service 还是 Blazor 项目,都离不开依赖注入。DI 提供了一种松耦合、可测试并易于维护的代码结构,也让整个应用的架构更加清晰。

本篇文章将从 DI 的核心概念、常见的注册方式到实际示例,帮助你真正理解 DI 的价值。


一、什么是依赖注入?

依赖注入是一种实现 控制反转(IoC) 的方式。

传统方式中,一个类如果需要一个服务,会自己在内部创建:

csharp 复制代码
public class OrderService
{
    private readonly EmailSender _emailSender = new EmailSender();

    public void CreateOrder()
    {
        _emailSender.Send("订单创建成功");
    }
}

这种写法的缺点:

  • 强耦合:OrderService 依赖 EmailSender 的实现
  • 难以测试:单元测试无法替换 EmailSender
  • 难以扩展:如果邮件发送方式改变,需要修改 OrderService 代码

而通过 DI,将依赖关系"注入"进来:

csharp 复制代码
public class OrderService
{
    private readonly IEmailSender _emailSender;

    public OrderService(IEmailSender emailSender)
    {
        _emailSender = emailSender;
    }

    public void CreateOrder()
    {
        _emailSender.Send("订单创建成功");
    }
}

这样 OrdersService 完全不关心 EmailSender 的具体实现,更加灵活、可扩展。


二、.NET 默认提供的三种服务生命周期

.NET 中注册依赖时,需要指定对象的生命周期,框架内置了三种方式:

1. Transient(瞬时)

每次请求都会创建一个新的实例

ini 复制代码
services.AddTransient<IEmailSender, EmailSender>();

适合:

  • 无状态对象
  • 轻量级、频繁使用的服务

2. Scoped(作用域)

在一个请求作用域内共享同一个实例,主要用于 Web 应用。

ini 复制代码
services.AddScoped<IOrderRepository, OrderRepository>();

适合:

  • 与数据库交互的服务
  • 需要在一次请求中共享数据的服务

3. Singleton(单例)

应用程序整个生命周期内只有一个实例。

ini 复制代码
services.AddSingleton<ICacheService, MemoryCacheService>();

适合:

  • 缓存类服务
  • 配置类服务

⚠️ 注意:Singleton 不能依赖 Scoped 服务,否则会产生 "依赖链生命周期问题"。


三、构造函数注入:最推荐的 DI 方式

构造函数注入是最常见、最推荐的注入方式:

csharp 复制代码
public class OrderService : IOrderService
{
    private readonly IEmailSender _emailSender;

    public OrderService(IEmailSender emailSender)
    {
        _emailSender = emailSender;
    }

    public void CreateOrder()
    {
        _emailSender.Send("您的订单已创建");
    }
}

优点:

  • 清晰地表达依赖关系
  • 便于单元测试
  • 强制依赖不可为空(可结合 C# 的 readonly/nullable

四、方法注入 & 属性注入

方法注入

依赖只用于方法内部时可以这样:

arduino 复制代码
public void SendOrder(IEmailSender emailSender)
{
    emailSender.Send("订单通知");
}

属性注入(不推荐)

arduino 复制代码
public IEmailSender EmailSender { get; set; }

缺点:

  • 依赖是否注入不明确
  • 可能导致空引用异常

五、实际示例:在 ASP.NET Core 中使用依赖注入

假设我们有一个订单服务:

1. 定义接口与实现

csharp 复制代码
public interface IOrderService
{
    void Create(string name);
}

public class OrderService : IOrderService
{
    private readonly ILogger<OrderService> _logger;

    public OrderService(ILogger<OrderService> logger)
    {
        _logger = logger;
    }

    public void Create(string name)
    {
        _logger.LogInformation($"订单 {name} 已创建");
    }
}

2. 注册服务

ini 复制代码
builder.Services.AddScoped<IOrderService, OrderService>();

3. 注入并使用

csharp 复制代码
[ApiController]
[Route("[controller]")]
public class OrderController : ControllerBase
{
    private readonly IOrderService _orderService;

    public OrderController(IOrderService orderService)
    {
        _orderService = orderService;
    }

    [HttpPost]
    public IActionResult Create(string name)
    {
        _orderService.Create(name);
        return Ok("订单创建成功");
    }
}

六、为什么需要依赖注入?

总结一下 DI 的核心价值:

  • 降低耦合:实现与使用分离
  • 提高可测试性:方便 Mock 依赖
  • 更易扩展:支持多实现切换
  • 提升代码结构清晰度

你会发现在大型项目中,DI 会让代码组织更加清晰,模块之间关系更明确。


结语

依赖注入是 .NET 中最重要的应用程序结构基础之一。掌握 DI 你就掌握了现代 .NET 架构的核心能力,无论是 Web API、微服务还是桌面应用,都离不开 DI 的支撑。

相关推荐
李拾叁的摸鱼日常2 小时前
从 Java 8 升级视角看Java 17 新特性详解
java·后端
Ankkaya2 小时前
小白服务器踩坑(2)- 自动化部署
后端
回家路上绕了弯2 小时前
Vavr 工具实用指南:Java 函数式编程的高效落地方案
分布式·后端
开心就好20252 小时前
没有 Mac 怎么上架 iOS 应用 跨平台团队的可行交付方案分析
后端
aiopencode2 小时前
构建可靠的 iOS 日志导出体系,从真机日志到系统行为的多工具协同实践
后端
期待のcode2 小时前
MyBatis-Plus通用Service
java·后端·mybatis·springboot
程序员-周李斌3 小时前
ArrayBlockingQueue 源码解析
java·开发语言·后端·哈希算法·散列表
该用户已不存在3 小时前
6款Vibe Coding工具,让开发从从容容游刃有余
后端·aigc·ai编程
qwepoilkjasd3 小时前
std::string详解
后端