依赖注入设计模式速查手册

一、核心概念

术语 说明
依赖注入(DI) 由外部负责创建和注入对象所需的依赖,而非对象自己 new 依赖实例
控制反转(IoC) 对象创建和生命周期管理的控制权从调用方反转到外部,DI 是 IoC 的一种实现方式
服务(Service) 被注入的依赖对象,通常通过接口抽象暴露能力
容器(Container) 负责管理服务注册、解析和生命周期的中间件(如 IServiceProvider
构造函数注入 通过构造函数参数传入依赖,最常用、最推荐的注入方式
属性注入 通过公共 setter 属性注入依赖,适用于可选依赖
方法注入 通过方法参数注入依赖,适用于一次性使用的场景
生命周期(Lifetime) 服务实例的存活时间:Transient(瞬态)、Scoped(作用域)、Singleton(单例)

二、常用操作

常用方式速查

方式 关键代码 适用场景
构造函数注入 public Cls(IDep dep) { _dep = dep; } 必需依赖,最推荐
属性注入 public IDep Dep { get; set; } 可选依赖
手动注册服务 services.AddTransient<IFoo, Foo>() .NET 内置容器
手动解析服务 serviceProvider.GetRequiredService<IFoo>() 从容器获取实例

1. 构造函数注入(最常用)

cs 复制代码
// 定义服务接口
public interface ICustomerRepository
{
    List<Customer> GetList();
    bool Add(Customer model);
}
​
// 实现服务
public class CustomerRepository : ICustomerRepository
{
    public List<Customer> GetList() => new List<Customer>();
    public bool Add(Customer model) => true;
}
​
// 通过构造函数注入依赖 ------ ViewModel 不再自己 new Repository
public class CustomerViewModel
{
    private readonly ICustomerRepository _repository;
​
    public CustomerViewModel(ICustomerRepository repository)
    {
        _repository = repository; // 外部注入,解耦
    }
​
    public void LoadData()
    {
        var customers = _repository.GetList();
    }
}

2. 在 MVVM 项目中的实际应用

cs 复制代码
// 银行系统中的 LoginViewModel ------ 通过构造函数注入 Repository 和 Window
public class LoginViewModel : ViewModelBase
{
    private readonly UserInfoRepository repository;
    private readonly Window loginWindow;
​
    // 构造函数注入:依赖从外部传入,ViewModel 不关心具体创建方式
    public LoginViewModel(UserInfoRepository repository, Window loginWindow)
    {
        this.repository = repository;
        this.loginWindow = loginWindow;
    }
​
    public RelayCommand LoginCommand
    {
        get => new RelayCommand((obj) =>
        {
            // 数据校验
            if (string.IsNullOrWhiteSpace(Account))
            {
                MessageBox.Show("账号不能为空!");
                return;
            }
​
            if (string.IsNullOrWhiteSpace(Password))
            {
                MessageBox.Show("密码不能为空!");
                return;
            }
​
            // 登录逻辑
            var exp = Expressionable.Create<ViewUserInfo>();
            exp.And(it => it.Account == Account);
            exp.And(it => it.Password == Password);
            exp.And(it => it.Status == 0);
            var list = repository.GetList(exp);
​
            if (list.Count > 1)
            {
                MessageBox.Show("账号重复!");
                return;
            }
            else if (list.Count < 1)
            {
                MessageBox.Show("账号或密码有误!");
                return;
            }
            else
            {
                LoginInfo.CurrentUser = list[0];
                loginWindow.DialogResult = true;
            }
        });
    }
}
​
// 使用时手动注入依赖(传统 WPF 项目无 DI 容器的写法)
var repository = new UserInfoRepository();
var viewModel = new LoginViewModel(repository, this);
this.DataContext = viewModel;

3. 常见注入方式对比

注入方式 适用场景 优点 缺点
构造函数注入 必需依赖 强制依赖明确、支持不可变字段 构造函数参数过多时臃肿
属性注入 可选依赖 灵活、不强制 依赖可能为 null,运行时才发现缺失
方法注入 一次性使用 作用域明确 不适合长期持有的依赖

4. 服务生命周期

生命周期 说明 典型场景
Transient(瞬态) 每次请求创建新实例 无状态服务、轻量级操作
Scoped(作用域) 同一作用域内共享实例 数据库 DbContext、请求级缓存
Singleton(单例) 全局唯一实例 配置服务、日志服务、缓存服务
cs 复制代码
// .NET 内置 DI 容器注册示例
public void ConfigureServices(IServiceCollection services)
{
    // 瞬态:每次注入都是新实例
    services.AddTransient<ICustomerRepository, CustomerRepository>();
​
    // 作用域:同一次请求内共享
    services.AddScoped<AppDbContext>();
​
    // 单例:全局唯一
    services.AddSingleton<ILogService, LogService>();
}

5. 手动实现简易 DI 容器

cs 复制代码
// 不依赖第三方框架,手写一个简易的服务容器
public class SimpleContainer
{
    private readonly Dictionary<Type, Func<object>> _registrations = new();
​
    // 注册服务:接口 -> 实现工厂
    public void Register<TInterface, TImplementation>()
        where TImplementation : TInterface, new()
    {
        _registrations[typeof(TInterface)] = () => new TImplementation();
    }
​
    // 注册单例
    public void RegisterSingleton<TInterface, TImplementation>()
        where TImplementation : TInterface, new()
    {
        var instance = default(TImplementation);
        _registrations[typeof(TInterface)] = () =>
        {
            instance ??= new TImplementation();
            return instance;
        };
    }
​
    // 解析服务
    public T Resolve<T>()
    {
        if (_registrations.TryGetValue(typeof(T), out var factory))
            return (T)factory();
        throw new InvalidOperationException($"服务 {typeof(T).Name} 未注册");
    }
}
​
// 使用示例
var container = new SimpleContainer();
container.Register<IUserRepository, UserRepository>();
var repo = container.Resolve<IUserRepository>();

三、问题排查

错误1:循环依赖(Circular Dependency)

  • 现象:构造函数注入时栈溢出或容器抛出异常

  • 原因:A 依赖 B,B 又依赖 A,形成死循环

  • 解决:提取共同依赖到第三个服务 C;或使用属性注入打破循环

cs 复制代码
// 错误:A 依赖 B,B 依赖 A
public class ServiceA { public ServiceA(ServiceB b) { } }
public class ServiceB { public ServiceB(ServiceA a) { } }
​
// 正确:提取共同逻辑到 ServiceC
public class ServiceC { /* 共同逻辑 */ }
public class ServiceA { public ServiceA(ServiceC c) { } }
public class ServiceB { public ServiceB(ServiceC c) { } }

错误2:未注册服务

  • 现象InvalidOperationException: No service for type 'XXX' has been registered

  • 原因:容器中未注册该服务类型

  • 解决 :在 ConfigureServices 中添加 services.AddTransient/Scoped/Singleton

错误3:生命周期不匹配

  • 现象:Scoped 服务被 Singleton 依赖时抛出异常或数据混乱

  • 原因:Singleton 生命周期长于 Scoped,导致捕获过期的 Scoped 实例

  • 解决 :改用 IServiceScopeFactory 在需要时创建新作用域

cs 复制代码
public class SingletonService
{
    private readonly IServiceScopeFactory _scopeFactory;
​
    public SingletonService(IServiceScopeFactory scopeFactory)
    {
        _scopeFactory = scopeFactory;
    }
​
    public void DoWork()
    {
        using var scope = _scopeFactory.CreateScope();
        var scopedService = scope.ServiceProvider.GetRequiredService<IScopedService>();
        // 安全使用 scoped 服务
    }
}
相关推荐
唐青枫7 小时前
线程不是越多越快:C#.NET Thread 生命周期、同步与后台工作线程实战
c#·.net
唐青枫1 天前
别只会反射:C#.NET Emit 动态生成代码实战详解
c#·.net
咕白m6251 天前
.NET 环境下 Word 超链接批量提取方案
c#·.net
用户91721561902111 天前
C# 通信协议增量解析:用状态机处理半包和粘包
c#
小码编匠2 天前
C# 工控上位机必备:数据转换工具类与十个核心模块
后端·c#·.net
唐青枫4 天前
别再乱用 StartNew:C#.NET TaskFactory 任务调度实战详解
c#·.net
Artech4 天前
[MAF预定义的AIContextProvider-03]ChatHistoryMemoryProvider——赋予Agent从经验中学习的能力
ai·c#·agent·memory·maf
Scout-leaf6 天前
C#摸鱼实录——IoC与DI案例详解
c#
咕白m6256 天前
使用 C# 在 Excel 中应用多种字体样式
后端·c#
Artech6 天前
[MAF预定义的AIContextProvider-02]AgentSkillsProvider——将Agent Skills引入MAF
ai·c#·agent·agent skills·maf