在简单注入器中,为什么单例或有作用域的服务依赖于瞬态服务会出错?

本文关键字:服务 依赖于 出错 作用域 注入器 简单 为什么 单例 | 更新日期: 2023-09-27 18:17:12

我使用的是简单注入器3.0.4

我有一个生活方式的服务,它的作用域依赖于另一个生活方式为transient的服务。

当我调用container.Verify()时,我得到一个关于生活方式不匹配的诊断错误。

导致问题的瞬态服务被注入到其他瞬态服务中,所以在我继续确定整个项目的范围之前,我需要问一下。为什么从任何生活方式的作用域到暂态的依赖是一个问题?瞬态物质每次被注射时都会被刷新,所以没有其他东西可以干扰它。从本质上讲,瞬态对象的生命周期是由它被注入的服务控制的。

我也已经从这里阅读了关于这个主题的文档,我确实理解为什么你不想要一个依赖于一个作用域服务的单例,但对瞬态的依赖总是安全的?

在简单注入器中,为什么单例或有作用域的服务依赖于瞬态服务会出错?

瞬态在每次注入时都被刷新,所以没有其他东西可以干扰它。

瞬态在每次从容器中请求它们时都会被更新,但是一旦它们被注入到组件中,它们就会一直存在,只要该组件存在。因此,如果消费组件是单例的,这意味着它将拖拽它所有的依赖项,使它们实际上也是单例的。当您查看通常如何实现依赖注入时,这种行为变得明显:

public class SomeComponent
{
    private readonly ILogger logger;
    private readonly IService service;
    public SomeComponent(ILogger logger, IService service) {
        this.logger = logger;
        this.service = service;
    }
}

正如你所看到的,依赖项存储在组件的私有字段中,只要SomeComponent存活,SomeComponent将继续使用相同的依赖项,它们就会保持存活。

从本质上讲,瞬态对象的生命周期是由它被注入的服务控制的。

准确;一个组件的寿命至少和它的消费者一样长。然而,一个依赖项可能有多个具有不同生活方式的消费者,因此很难看到该依赖项将存在多长时间。当注入到消费者1时,它可能会在请求期间存活,而注入到消费者2的该依赖的另一个实例将与应用程序一样存活。

就像作用域实例一样,瞬态注册通常不是线程安全的;否则,您就会将它们注册为单例。将瞬态保持较长时间的活动,显然会导致并发性错误或与陈旧数据相关的错误。这就是为什么Simple Injector默认不允许这样做并抛出异常的原因。

你可能会对Autofac如何定义它的生活方式感到困惑,与Simple Injector相比。Autofac不包含短暂的生活方式。相反,它有一种InstancePerDependency生活方式。从技术上讲,这与瞬态是一样的,但意图是非常不同的。对于InstancePerDependency,你可以说:"这个组件打算和它的消费者一样长寿,不管他们的生活方式是什么。"也许在某些情况下这是有意义的,但这样做实际上是忽略了房间里的大象,我已经看到缺乏检测是bug的常见来源。在大多数情况下,如果你不关心组件的生活方式,这意味着它应该被注册为单例——而不是InstancePerDependency

简单注入器不允许将瞬态注入到有作用域的实例中是因为有作用域的实例也可以存活很长时间(取决于应用),你不能总是假设瞬态可以安全地注入到有作用域的消费者中。

最后,这一切都是关于沟通你的代码的意图。如果组件是无状态的或线程安全的,你应该将它注册为单例。如果它不是线程安全的,则将其注册为作用域或瞬态。这使得任何阅读配置的人都能清楚地知道他应该如何处理这样的组件,并且它允许Simple Injector为你检测任何错误配置。

当Simple Injector为你检测错误配置时,我得出的结论是,当你的系统是围绕纯粹由单例组件组成的对象图设计时,你的DI配置可以大大简化。我在这里表达了这些想法。这将消除我们在使用依赖注入时所面临的许多复杂性,甚至比依赖注入本身更快地暴露出违反SOLID原则的情况。

在我确定整个项目的范围之前

那是我不建议做的事。您通常会看到,在您的应用程序中只有一些"叶子组件"是有作用域的(比如DbContext)。这些作用域组件不依赖于许多其他组件。您自己编写的组件通常应该是无状态的,不需要任何缓存。因此,如果对象图是单例的(还没有)一个选项,我通常会使尽可能多的对象图是瞬态的,只有那些少数叶组件作用域。因为瞬态可以安全地依赖于作用域实例,所以一切都会如预期的那样工作。