一、依赖注入是个啥玩意儿
咱们写代码的时候,经常需要创建对象,然后把这些对象传递给其他类使用。以前的做法可能是直接在类里面new一个对象出来,但这样会导致代码耦合度太高,测试起来也麻烦。依赖注入(Dependency Injection,简称DI)就是为了解决这个问题而生的。
简单来说,依赖注入就是把对象的创建和管理交给框架来做,我们只需要告诉框架:“我需要这个服务,你帮我搞定。” 在DotNetCore里,依赖注入是内置的,开箱即用,非常方便。
二、常见的依赖注入异常
虽然依赖注入好用,但用不好也会踩坑。下面列举几个常见的异常,以及它们的解决办法。
1. InvalidOperationException: Unable to resolve service for type 'XXX'
这个错误的意思是:“框架找不到你要的服务啊!” 通常是因为没注册服务就尝试使用它。
// 错误示例:没注册服务就直接用
public class MyController : Controller
{
private readonly IMyService _myService;
public MyController(IMyService myService) // 这里会报错,因为IMyService没注册
{
_myService = myService;
}
}
// 正确做法:先在Startup.cs里注册服务
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IMyService, MyService>(); // 注册服务
}
2. System.InvalidOperationException: A circular dependency was detected
循环依赖是指两个或多个服务互相依赖,比如A依赖B,B又依赖A,框架没法处理这种情况。
// 错误示例:循环依赖
public class ServiceA
{
public ServiceA(ServiceB b) { } // A依赖B
}
public class ServiceB
{
public ServiceB(ServiceA a) { } // B又依赖A,死循环了
}
// 解决办法:重构代码,避免循环依赖
// 比如引入第三个服务,或者用懒加载(Lazy<T>)
3. ObjectDisposedException: Cannot access a disposed object
这个错误通常发生在使用Scoped或Transient服务时,对象已经被释放了,但你还想用。
// 错误示例:在单例服务里使用Scoped服务
public class SingletonService
{
private readonly ScopedService _scopedService;
public SingletonService(ScopedService scopedService)
{
_scopedService = scopedService; // 这里会出问题,因为Singleton生命周期比Scoped长
}
}
// 解决办法:避免在长生命周期的服务里依赖短生命周期的服务
// 或者改用工厂模式(IServiceProvider)
三、依赖注入的生命周期
DotNetCore的依赖注入有三种生命周期:
- Transient:每次请求都创建一个新实例。
- Scoped:同一个请求内共享一个实例。
- Singleton:整个应用程序生命周期内只有一个实例。
// 示例:不同生命周期的注册方式
services.AddTransient<ITransientService, TransientService>(); // 每次都是新的
services.AddScoped<IScopedService, ScopedService>(); // 同请求内相同
services.AddSingleton<ISingletonService, SingletonService>(); // 全局唯一
注意事项:
- 不要在
Singleton服务里依赖Scoped服务,否则会出问题。 Scoped服务在非Web环境(如控制台应用)下默认会退化为Singleton,需要手动处理。
四、高级玩法:工厂模式与懒加载
有时候,我们不想一上来就创建所有服务,而是按需加载。这时候可以用Lazy<T>或者工厂模式。
// 使用Lazy<T>延迟加载
public class MyController : Controller
{
private readonly Lazy<IMyService> _lazyService;
public MyController(Lazy<IMyService> lazyService)
{
_lazyService = lazyService;
}
public IActionResult Index()
{
var service = _lazyService.Value; // 真正用到的时候才初始化
return View();
}
}
// 使用IServiceProvider工厂模式
public class MyController : Controller
{
private readonly IServiceProvider _serviceProvider;
public MyController(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public IActionResult Index()
{
var service = _serviceProvider.GetService<IMyService>(); // 手动获取服务
return View();
}
}
五、总结
依赖注入是DotNetCore的核心功能,用好了能让代码更清晰、更易测试。但也要注意生命周期管理,避免循环依赖和对象释放问题。
最佳实践:
- 尽量使用构造函数注入,而不是手动
new对象。 - 注意服务的生命周期,避免跨作用域依赖。
- 遇到复杂场景时,可以用工厂模式或懒加载。
希望这篇文章能帮你解决依赖注入的坑,写代码更顺畅!
评论