一、依赖注入是个啥玩意儿

咱们写代码的时候,经常需要创建对象,然后把这些对象传递给其他类使用。以前的做法可能是直接在类里面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依赖BB又依赖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

这个错误通常发生在使用ScopedTransient服务时,对象已经被释放了,但你还想用。

// 错误示例:在单例服务里使用Scoped服务
public class SingletonService
{
    private readonly ScopedService _scopedService;

    public SingletonService(ScopedService scopedService) 
    {
        _scopedService = scopedService; // 这里会出问题,因为Singleton生命周期比Scoped长
    }
}

// 解决办法:避免在长生命周期的服务里依赖短生命周期的服务
// 或者改用工厂模式(IServiceProvider)

三、依赖注入的生命周期

DotNetCore的依赖注入有三种生命周期:

  1. Transient:每次请求都创建一个新实例。
  2. Scoped:同一个请求内共享一个实例。
  3. 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的核心功能,用好了能让代码更清晰、更易测试。但也要注意生命周期管理,避免循环依赖和对象释放问题。

最佳实践

  1. 尽量使用构造函数注入,而不是手动new对象。
  2. 注意服务的生命周期,避免跨作用域依赖。
  3. 遇到复杂场景时,可以用工厂模式或懒加载。

希望这篇文章能帮你解决依赖注入的坑,写代码更顺畅!