一、核心概念
| 术语 | 说明 |
|---|---|
| 依赖注入(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. 构造函数注入(最常用)
// 定义服务接口 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 项目中的实际应用
// 银行系统中的 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(单例) | 全局唯一实例 | 配置服务、日志服务、缓存服务 |
// .NET 内置 DI 容器注册示例 public void ConfigureServices(IServiceCollection services) { // 瞬态:每次注入都是新实例 services.AddTransient<ICustomerRepository, CustomerRepository>(); // 作用域:同一次请求内共享 services.AddScoped<AppDbContext>(); // 单例:全局唯一 services.AddSingleton<ILogService, LogService>(); }5. 手动实现简易 DI 容器
// 不依赖第三方框架,手写一个简易的服务容器 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;或使用属性注入打破循环
// 错误: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在需要时创建新作用域
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 服务 } }