【问题标题】:In Simple Injector why is it an error for a singleton or scoped service to depend on a transient service在 Simple Injector 中,为什么单例或范围服务依赖瞬态服务是错误的
【发布时间】:2015-10-12 12:25:25
【问题描述】:

我正在使用简单的注射器 3.0.4

我有一个服务,它的生活方式依赖于一个服务,它的生活方式是短暂的。

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

导致问题的瞬态服务被注入到其他瞬态服务中,因此在我继续进行整个项目范围之前,我需要询问。为什么从任何生活方式的范围到 transient 的依赖性是一个问题?瞬态在每次注入时都会更新,因此没有其他任何东西可以干扰它。本质上,瞬态对象的生命周期由注入它的服务控制。

另外,我已经阅读了来自 here 的关于此主题的文档,我明白为什么您不希望单例依赖于范围服务,但肯定依赖瞬态总是安全的?

【问题讨论】:

    标签: c# dependency-injection simple-injector


    【解决方案1】:

    瞬态在每次注入时都会更新,因此没有其他东西可以干扰它。

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

    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 默认不允许这样做并抛出异常的原因。

    与 Simple Injector 相比,您可能会对 Autofac 如何定义其生活方式感到困惑。 Autofac 不包含 Transient 生活方式。相反,它具有InstancePerDependency 的生活方式。从技术上讲,这与瞬态相同,但意图却大不相同。对于InstancePerDependency,您会说:“该组件旨在与它的消费者一样长寿,无论这种生活方式可能是什么”。可能在某些情况下这是有道理的,但这样做实际上是在忽略房间里的大象,而且我发现缺乏检测是常见的错误来源。在大多数情况下,如果你不关心组件的生活方式,这意味着它应该被注册为单例——而不是InstancePerDependency

    Simple Injector 不允许将瞬态注入到作用域实例中的原因是,作用域实例也可以存在很长时间(取决于应用程序),并且您不能总是假设可以安全地注入瞬变进入范围消费者。

    最后,一切都是为了传达代码的意图。如果组件是无状态或线程安全的,则应将其注册为单例。它不是线程安全的,您将其注册为作用域或瞬态。这让任何阅读配置的人都清楚他应该如何处理此类组件,并且它允许 Simple Injector 为您检测任何错误配置。

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

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

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

    【讨论】:

    • 这是一个全面的解释,我想我明白了——这都是关于线程安全的。我的项目的组织方式是多个线程只使用单例。范围和瞬态不在线程之间共享。 Transient 是我们的默认设置,有 1 或 2 个单例和一些作用域。虽然错误消息现在令人沮丧,但我可以看到,如果它发现了我的一个单身人士依赖于瞬态的情况,它如何帮助我躲避子弹。
    • 我不明白。我可以将线程安全类作为“瞬态”依赖项,注入到单例和其他瞬态类中。 “生活方式”不一定需要与线程安全相关联。 (单例线程安全,瞬态非线程安全)
    • 不,我不想要不同的单例——我只是不想强迫我的单例的注入成员自己成为单例。我不明白为什么这是必要的,或者“导致错误”。
    • @ixSci 每个图书馆都会产生限制。是的,Simple Injector 比其他 DI 库更加固执己见。但请注意,没有硬性限制阻止您将瞬态注入单例;这只是明智的默认设置。您可以suppress these warnings 或定义自己的InstancePerDependencyLifestyle
    • 感谢您的解决方法 - 它有所帮助。至于对我的设计提出建议的建议:问题是我认为我的设计没有任何问题我认为你对生活方式关系的推理存在缺陷,所以我认为应该由你来改变设计不是我:)。您仍然做出了选择,我们(用户)应该欣赏它或选择另一个容器。
    猜你喜欢
    • 1970-01-01
    • 2021-09-08
    • 2019-03-17
    • 1970-01-01
    • 2020-08-05
    • 2015-10-31
    • 2017-10-18
    • 2012-09-26
    • 2017-05-12
    相关资源
    最近更新 更多