c# 依赖注入 di 是什么

来源:这里教程网 时间:2026-02-21 17:38:27 作者:

它不是语法糖,也不是框架黑魔法——C# 中的依赖注入(DI)是一种把“谁来创建对象”的控制权交出去的设计实践。核心就一句话:

类不自己 new 依赖,而是让外部把依赖塞进来

为什么非得用 DI?不写 new 就行了?

不用 DI 的典型写法是这样的:

public class OrderService
{
    private readonly SqlOrderRepository _repo = new SqlOrderRepository(); // ❌ 自己 new,硬编码
    public void Process(Order order) => _repo.Save(order);
}

问题立刻浮现:

SqlOrderRepository
一换(比如改成
FileOrderRepository
),你得改
OrderService
源码 —— 违反开闭原则
单元测试时没法塞个
MockOrderRepository
进去,测试会真连数据库 —— 测试慢、不稳定、难隔离
所有地方都
new
同一个类,连接字符串、重试策略等配置散落在各处 —— 难统一管理

DI 怎么做?三步走,缺一不可

不是加个 NuGet 包就叫用了 DI,必须完成这三件事:

声明抽象:定义接口,如
IOrderRepository
,只管“能做什么”,不管“怎么做”
注册实现:在
Program.cs
里告诉容器:“当有人要
IOrderRepository
,就给一个
SqlOrderRepository
实例”,例如:
builder.Services.AddScoped<iorderrepository sqlorderrepository>();</iorderrepository>
接收依赖:在构造函数里写参数,让容器自动填值,例如:
public OrderService(IOrderRepository repo) { _repo = repo; }

漏掉任意一步,运行时就会抛出

InvalidOperationException: No service for type 'X' has been registered

生命周期选错,bug 会半夜找你

注册时选的生命周期不是“随便点一个”,它直接决定对象是否共享、线程安全、内存泄漏风险:

AddTransient()
:每次请求都新建 —— 适合无状态工具类(如
IMapper
),但别用它注册 DbContext
AddScoped()
:一次 HTTP 请求内复用(ASP.NET Core 默认作用域)——
DbContext
必须用这个,否则并发写入会崩
AddSingleton()
:整个应用生命周期只一个实例 —— 适合配置类、缓存管理器,但若内部持有非线程安全资源(如
StreamWriter
),多线程下大概率出错

最常踩的坑是:把本该

Scoped
的仓储注册成
Singleton
,结果数据库上下文跨请求复用,报错
A second operation started on this context

DI 真正难的不是写那几行注册代码,而是想清楚哪些该抽象、哪些该共享、哪些该隔离 —— 这些判断一旦定错,后期重构成本远高于初期多花十分钟设计接口。

相关推荐