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

相关推荐
小马爱打代码9 小时前
SpringBoot:封装 starter
java·spring boot·后端
STARSpace888810 小时前
SpringBoot 整合个推推送
java·spring boot·后端·消息推送·个推
Marktowin10 小时前
玩转 ZooKeeper
后端
蓝眸少年CY11 小时前
(第十二篇)spring cloud之Stream消息驱动
后端·spring·spring cloud
码界奇点11 小时前
基于SpringBoot+Vue的前后端分离外卖点单系统设计与实现
vue.js·spring boot·后端·spring·毕业设计·源代码管理
lindd91191111 小时前
4G模块应用,内网穿透,前端网页的制作第七讲(智能头盔数据上传至网页端)
前端·后端·零基础·rt-thread·实时操作系统·项目复刻
Loo国昌12 小时前
【LangChain1.0】第八阶段:文档处理工程(LangChain篇)
人工智能·后端·算法·语言模型·架构·langchain
vx_bisheyuange12 小时前
基于SpringBoot的海鲜市场系统
java·spring boot·后端·毕业设计
李慕婉学姐13 小时前
【开题答辩过程】以《基于Spring Boot和大数据的医院挂号系统的设计与实现》为例,不知道这个选题怎么做的,不知道这个选题怎么开题答辩的可以进来看看
大数据·spring boot·后端
源代码•宸14 小时前
Leetcode—3. 无重复字符的最长子串【中等】
经验分享·后端·算法·leetcode·面试·golang·string