15

There is a problem of not correctly configured DI containers named Captive Dependency by Mark Seemann. It's obviously, for example, when a "PerCall" dependency is injected into a "Singleton" one. But what about a scenario when "Transient" is injected into "Singleton"? It’s not so obvious to me why I shouldn’t do that especial for some scenarios like a timer registered as transient injected into a singleton service and lives there forever.

There is Lifestyle Mismatches diagnostic warning in SimpleInjector DI container. The description says:

it is safe for a transient component to depend on a singleton, but not the other way around

Why is it bad? A good example of bad things in this case will be extremely useful. Is there any limitation when it "always" bad or some scenarios can be allowed (see my example above)?

Steven
  • 166,672
  • 24
  • 332
  • 435
hcp
  • 3,268
  • 6
  • 26
  • 41
  • https://www.tektutorialshub.com/asp-net-core/asp-net-core-dependency-injection-lifetime/#injecting-service-with-different-lifetimes-into-another – Roman Koliada May 24 '19 at 10:05
  • @Ric.Net thank you, but I already have provided the same link in my question – hcp May 24 '19 at 10:34
  • @hcp `Transient` and per call are more or less the same. A `PerCall` lifecycle is a transient that's kept alive only as long as the `call` scope exists. `Transient` has an even shorter lifecycle than `PerCall`. The DI Container will call `Dispose` on both objects once they get out of scope – Panagiotis Kanavos May 24 '19 at 11:10
  • @hcp it's not about thread safety or keeping instances, the problem is calling objects that have already been disposed – Panagiotis Kanavos May 24 '19 at 11:13

2 Answers2

18

It depends on how you define what "Transient" means. The Simple Injector documentation, for instance, states:

Simple Injector considers Transient registrations to be lasting for only a short time; temporary, i.e. short lived and not reused. For that reason, Simple Injector prevents the injection of Transient components into Singleton consumers as they are expected to be longer lived, which would otherwise result in Lifestyle Mismatches.

But other DI Containers use a different definition for "Transient." The .NET Core DI Container (MS.DI), for instance, advises your transient registrations to be "lightweight, stateless services."

As they are assumed stateless, it is safe to inject them into consumers with any other lifetime, as long as they don’t have any stateful (sub) dependencies of their own. “Stateful,” in the context of MS.DI, typically means a scoped dependency. MS.DI's definition of transient is identical to what Autofac calls Instance Per Dependency. IMO, Autofac's naming is more correct, because conceptually, there is a big difference between the two definitions of transient, and I believe most DI Containers follow the "lasting for only a short time; temporary" definition.

As long as your Transient component is stateless and as long as that component contains no stateful dependencies, there is no harm in injecting it into a singleton (or scoped) consumer. Injecting such stateless component into a singleton, however, still makes that component long lived, which is something completely different than being short lived. As Simple Injector doesn't know whether or not your component contains state, it considers all transients to be short lived and, therefore, warns you about injecting transients into singletons.

Steven
  • 166,672
  • 24
  • 332
  • 435
16

It's not safe because your transient instance will be forever inside singleton.

Example: you can inject singleton into some another class instance. In that case you will indirectly inject transient instance too. So you can face with thread-safety problems for instance.

If you register class as a singleton you consider it as a thread-safe because you can use the same instance concurrently in few threads. If you register class as transient most likely it's a lightweight and not thread-safe class. That's why you registered it as transient not as singleton, right? And that's why you see warnings: it's not safe to use transient objects concurrently. In asp.net core embedded IoC you will face runtime exception in such case.

Imagine: your transient class creates SqlConnection inside and keeps it opened during its lifetime and allows to use it without synchronization. And that's OK because it's considered to be alive for a short period of time in one thread. Then you inject it into singleton and then you use this singleton in each request.

mtkachenko
  • 5,389
  • 9
  • 38
  • 68
  • 3
    I understand that a transient instance will be forever inside the singleton. Could you provide more detailed example, when it's can be bad? "thread-safety" problems is too general words. Can you example more detailed? May be some code? Thank you. – hcp May 24 '19 at 10:15
  • 1
    @hcp udated the answer – mtkachenko May 24 '19 at 10:49
  • `your transient instance will be forever inside singleton.` that's wrong. The problem is the same as PerCall, The singleton is keeping a *reference* to an instance that's *disposed* when the `call` scope ends. This isn't about thread safety. Even with a *single* thread the singleton will end up calling a disposed object at some point – Panagiotis Kanavos May 24 '19 at 11:12
  • 2
    @PanagiotisKanavos: "The singleton is keeping a reference to an instance that's disposed when the call scope ends.". This statement is not completely correct. With MS.DI, for instance, transients that are injected into a singleton are only disposed when the container gets disposed. With Simple Injector, on the other hand, transients are *never* disposed. – Steven May 24 '19 at 11:19