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

相关推荐
小满zs24 分钟前
Go语言第三章(五谷轮回)
后端·go
霸道流氓气质5 小时前
SpringBoot中通用工具类库(Utils)封装与使用实践
spring boot·后端·python
MemoleCard8 小时前
深度解析Java当中的锁机制
后端
wear工程师8 小时前
ThreadLocal 在线程池里为什么会串数据:别只会答内存泄漏
java·后端
whyfail9 小时前
前端学 Spring Boot(2):一次点击,如何穿过整个后端?
前端·spring boot·后端
程序员cxuan11 小时前
Loop 还没玩明白,Graph Engineering 又火了。
人工智能·后端·程序员
uzong11 小时前
把一面面试总结做成一个 Skill:重复的事,就别每次都重写
后端·面试
程序员海军12 小时前
一个 AI 应用开发程序员的一天,都在屏幕前忙些什么?
前端·后端·程序员
不能放弃治疗12 小时前
MCP
后端