一、核心概念
| 术语 | 说明 |
|---|---|
| 依赖注入(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 服务
}
}