深入理解 .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 的支撑。

相关推荐
苏三的开发日记几秒前
flink集群服务搭建
后端
程序帝国4 分钟前
SpringBoot整合RediSearch(完整,详细,连接池版本)
java·spring boot·redis·后端·redisearch
源码获取_wx:Fegn089533 分钟前
基于springboot + vueOA工程项目管理系统
java·vue.js·spring boot·后端·spring
小哀239 分钟前
2025年总结: 我还在往前走
前端·后端·全栈
一 乐43 分钟前
健康管理|基于springboot + vue健康管理系统(源码+数据库+文档)
java·前端·数据库·vue.js·spring boot·后端·学习
DICOM医学影像1 小时前
15. Go-Ethereum测试Solidity ERC20合约 - Go-Ethereum调用合约方法
开发语言·后端·golang·区块链·智能合约·以太坊·web3.0
superman超哥1 小时前
Rust 过程宏开发入门:编译期元编程的深度实践
开发语言·后端·rust·元编程·rust过程宏·编译期
bluetata1 小时前
在 Spring Boot 中使用 Amazon Textract 从图像中提取文本
java·spring boot·后端
李梨同学丶2 小时前
2025年终总结
后端
苏三的开发日记2 小时前
flume集成kafka
后端