2026-07-05 · .NET · 阅读约 6 分钟

.NET 依赖注入容器使用心得

依赖注入是 .NET Core 之后的核心特性,但三种生命周期经常搞混。刚开始学的时候觉得"不就是在构造函数里加个参数吗",真遇到运行时报错才发现生命周期没选对会出各种奇怪问题。这篇笔记整理一下自己的理解。

一、三种生命周期

生命周期注册方法实例数量
Singleton 单例AddSingleton整个应用只有一个实例
Scoped 作用域AddScoped每个请求一个实例
Transient 瞬时AddTransient每次请求都新建

二、注册写法

var builder = WebApplication.CreateBuilder(args);

// 接口 + 实现
builder.Services.AddSingleton<ILogger, ConsoleLogger>();
builder.Services.AddScoped<IUserService, UserService>();
builder.Services.AddTransient<INotification, EmailNotification>();

三、常见误区

四、数据库上下文的例子

EF Core 的 DbContext 默认注册为 Scoped,因为它内部维护了一个工作单元(Unit of Work),应该在一个请求结束时释放:

builder.Services.AddDbContext<AppDbContext>(
    options => options.UseSqlServer(connString));

这也是为什么不能把 DbContext 注入到 Singleton 服务里——一个请求里改的数据还没保存,下一个请求就拿到了旧实例。这个报错信息第一次看到时完全没反应过来,后来查了资料才明白是生命周期不匹配。

五、构造函数注入

正确做法是在构造函数里声明依赖,容器会自动传进来:

public class OrderService
{
    private readonly IRepository _repo;

    public OrderService(IRepository repo)
    {
        _repo = repo;  // 容器自动注入
    }
}

这样写的好处是:依赖关系一目了然,单元测试时也可以方便地替换成 mock 实现。不要在服务里自己 new 依赖对象,那样就失去了 DI 的意义。