在 Simple Injector 中,为什么单例或作用域服务依赖于瞬态服务是错误的
In Simple Injector why is it an error for a singleton or scoped service to depend on a transient service
我使用的是 simple injector 3.0.4
我有一个生活方式为 scoped 的服务依赖于一个生活方式为 transient 的服务。
当我调用 container.Verify() 时,我收到有关生活方式不匹配的诊断错误。
导致问题的瞬态服务被注入到其他瞬态服务中,因此在我继续并确定我的整个项目范围之前,我需要先询问一下。为什么从任何生活方式的范围到 transient 的依赖是一个问题?每次注入瞬态时都会更新瞬态,因此没有其他东西可以干扰它。本质上,瞬态对象的生命周期由它注入的服务决定。
此外,我已经从 here 阅读了有关此主题的文档,并且我确实理解为什么您不希望单例依赖于作用域服务,但肯定对瞬态的依赖总是安全的?
Transients are newed up fresh each time they are injected so nothing else can interfere with it.
每次您从容器中请求瞬态时,瞬态都会更新,但是一旦它们被注入到组件中,只要该组件存在,它们就会一直存在。因此,如果消费组件是单例,这意味着它会拖拽它的所有依赖项,使它们实际上也成为单例。当您查看通常如何实现依赖注入时,这种行为会变得很明显:
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
将继续使用相同的依赖项。
In essence the lifetime of a transient object is governed by the service it is injected into.
完全正确;组件的生命周期至少与其消费者一样长。然而,一个依赖项可以有多个生活方式不同的消费者,这使得很难看出依赖项可以存活多久。当注入到消费者 1 中时,它可能会在请求期间存在,而注入到消费者 2 中的该依赖项的另一个实例将在应用程序存在时存在。
就像作用域实例一样,瞬态注册通常不是线程安全的;否则你会把它们注册为单例。让瞬态保持更长的时间,显然会导致并发错误或与陈旧数据相关的错误。这就是为什么 Simple Injector 默认不允许这样做并抛出异常的原因。
与 Simple Injector 相比,您可能会对 Autofac 如何定义其生活方式感到困惑。 Autofac 不包含 Transient 生活方式。相反,它有一种 InstancePerDependency
的生活方式。从技术上讲,这与 transient 相同,但意图却大不相同。使用 InstancePerDependency
你说:"This component is intended to live as long as its consumer, whatever that lifestyle might be"。在某些情况下,这可能是有意义的,但这样做实际上是在忽略房间里的大象,而且我发现缺乏检测是错误的常见来源。在大多数情况下,如果您不关心组件的生活方式,则意味着它应该注册为单例——而不是 InstancePerDependency
.
Simple Injector 不允许将瞬变注入作用域实例的原因是因为作用域实例也可以存活很长时间(取决于应用程序)并且您不能总是假设瞬变可以安全地被注入范围内的消费者。
最后,一切都是为了传达代码的意图。如果组件是无状态或线程安全的,您应该将其注册为单例。它不是线程安全的,您将其注册为作用域或瞬态。这让阅读配置的任何人都清楚他应该如何处理此类组件,并且它允许 Simple Injector 为您检测任何错误配置。
虽然 Simple Injector 会为您检测错误配置,但我得出的结论是,当您的系统围绕纯粹由单例组件组成的对象图设计时,您的 DI 配置可以大大简化。我表达了这些想法。这将消除我们在使用依赖注入时面临的许多复杂性,甚至比 DI 本身更快地暴露违反 SOLID 原则的行为。
before I go ahead and make my whole project scoped
我不建议这样做。您通常会看到应用程序中只有某些 'leaf components' 是有范围的(例如 DbContext
)。这些范围内的组件不依赖于许多其他组件。您自己编写的组件通常应该是无状态的,不需要任何缓存。因此,如果使对象图单例成为(尚未)一个选项,我通常会尽可能多地使对象图成为瞬态的,并且只有那些少数叶组件具有作用域。由于瞬变可以安全地依赖于范围内的实例,因此一切都会按预期工作。
我使用的是 simple injector 3.0.4
我有一个生活方式为 scoped 的服务依赖于一个生活方式为 transient 的服务。
当我调用 container.Verify() 时,我收到有关生活方式不匹配的诊断错误。
导致问题的瞬态服务被注入到其他瞬态服务中,因此在我继续并确定我的整个项目范围之前,我需要先询问一下。为什么从任何生活方式的范围到 transient 的依赖是一个问题?每次注入瞬态时都会更新瞬态,因此没有其他东西可以干扰它。本质上,瞬态对象的生命周期由它注入的服务决定。
此外,我已经从 here 阅读了有关此主题的文档,并且我确实理解为什么您不希望单例依赖于作用域服务,但肯定对瞬态的依赖总是安全的?
Transients are newed up fresh each time they are injected so nothing else can interfere with it.
每次您从容器中请求瞬态时,瞬态都会更新,但是一旦它们被注入到组件中,只要该组件存在,它们就会一直存在。因此,如果消费组件是单例,这意味着它会拖拽它的所有依赖项,使它们实际上也成为单例。当您查看通常如何实现依赖注入时,这种行为会变得很明显:
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
将继续使用相同的依赖项。
In essence the lifetime of a transient object is governed by the service it is injected into.
完全正确;组件的生命周期至少与其消费者一样长。然而,一个依赖项可以有多个生活方式不同的消费者,这使得很难看出依赖项可以存活多久。当注入到消费者 1 中时,它可能会在请求期间存在,而注入到消费者 2 中的该依赖项的另一个实例将在应用程序存在时存在。
就像作用域实例一样,瞬态注册通常不是线程安全的;否则你会把它们注册为单例。让瞬态保持更长的时间,显然会导致并发错误或与陈旧数据相关的错误。这就是为什么 Simple Injector 默认不允许这样做并抛出异常的原因。
与 Simple Injector 相比,您可能会对 Autofac 如何定义其生活方式感到困惑。 Autofac 不包含 Transient 生活方式。相反,它有一种 InstancePerDependency
的生活方式。从技术上讲,这与 transient 相同,但意图却大不相同。使用 InstancePerDependency
你说:"This component is intended to live as long as its consumer, whatever that lifestyle might be"。在某些情况下,这可能是有意义的,但这样做实际上是在忽略房间里的大象,而且我发现缺乏检测是错误的常见来源。在大多数情况下,如果您不关心组件的生活方式,则意味着它应该注册为单例——而不是 InstancePerDependency
.
Simple Injector 不允许将瞬变注入作用域实例的原因是因为作用域实例也可以存活很长时间(取决于应用程序)并且您不能总是假设瞬变可以安全地被注入范围内的消费者。
最后,一切都是为了传达代码的意图。如果组件是无状态或线程安全的,您应该将其注册为单例。它不是线程安全的,您将其注册为作用域或瞬态。这让阅读配置的任何人都清楚他应该如何处理此类组件,并且它允许 Simple Injector 为您检测任何错误配置。
虽然 Simple Injector 会为您检测错误配置,但我得出的结论是,当您的系统围绕纯粹由单例组件组成的对象图设计时,您的 DI 配置可以大大简化。我表达了这些想法
before I go ahead and make my whole project scoped
我不建议这样做。您通常会看到应用程序中只有某些 'leaf components' 是有范围的(例如 DbContext
)。这些范围内的组件不依赖于许多其他组件。您自己编写的组件通常应该是无状态的,不需要任何缓存。因此,如果使对象图单例成为(尚未)一个选项,我通常会尽可能多地使对象图成为瞬态的,并且只有那些少数叶组件具有作用域。由于瞬变可以安全地依赖于范围内的实例,因此一切都会按预期工作。