7

I'm using simple injector 3.0.4

I have a service that with a lifestyle of scoped dependent on a service which has a lifestyle of transient.

When I call container.Verify() I get a diagnostic error about lifestyle mismatch.

The transient service causing the issues is injected into other transient services so before I go ahead and make my whole project scoped I need to ask. Why is a dependency from a scope of any lifestyle to transient an issue? Transients are newed up fresh each time they are injected so nothing else can interfere with it. In essence the lifetime of a transient object is governed by the service it is injected into.

Also I have already read the documentation on this subject from here, and I do understand why you wouldn't want a singleton dependent on a scoped service for instance but surely a dependency on transient is always safe?

Steven
  • 166,672
  • 24
  • 332
  • 435
Twisted
  • 2,939
  • 4
  • 32
  • 54

1 Answers1

13

Transients are newed up fresh each time they are injected so nothing else can interfere with it.

Transients are newed up every time you request them from the container, but once they are injected into a component, they will stick around for as long as that component lives. So if the consuming component is a singleton, this means that it will drag along all its dependencies, making them practically singletons as well. This behaviour becomes obvious when you look at how you would typically implement dependency injection:

public class SomeComponent
{
    private readonly ILogger logger;
    private readonly IService service;

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

As you can see, the dependencies are stored in private fields of the component, will cause them to stay alive for as long as SomeComponent lives and SomeComponent will keep using the same dependencies.

In essence the lifetime of a transient object is governed by the service it is injected into.

Exactly; a component's lifestyle will be at least as long as its consumer. However, a dependency can have multiple consumers with different lifestyles, making it really hard to see how long the dependency will live. When injected into consumer 1 it might live for the duration of the request, while another instance of that dependency, injected into consumer 2, will live for as long as the application does.

Just as scoped instances, transient registrations are typically not thread-safe; otherwise you would have registered them as singleton. Keeping transients alive for a longer period of time, can obviously cause concurrency bugs or bugs related to stale data. This is why Simple Injector disallows this by default and throws an exception.

You might be confused by how Autofac defines its lifestyles, compared to Simple Injector. Autofac does not contain a Transient lifestyle. Instead it has an InstancePerDependency lifestyle. Technically this is the same as transient, but the intent is very different. With InstancePerDependency you say: "This component is intended to live as long as its consumer, whatever that lifestyle might be". There might be cases where this makes sense, but by doing this you are actually ignoring the elephant in the room and I've seen the lack of detection to be a common source of bugs. In most cases, if you don’t care about a components lifestyle, it means it should be registered as singleton – not InstancePerDependency.

The reason that Simple Injector doesn't allow transients to be injected into scoped instances is because scoped instances as well can live for a long time (depending on the application) and you can't always assume that transients can safely be injected into scoped consumers.

In the end it is all about communicating the intent of your code. In case a component is stateless or thread-safe, you should register it as singleton. It it's not thread-safe, you register it as scoped or transient. This makes it clear to anyone who reads the configuration how he should handle such component AND it allows Simple Injector to detect any misconfigurations for you.

While Simple Injector detects misconfigurations for you, I came to the conclusion that your DI configuration can be simplified considerably when your system is designed around object graphs that consist purely of singleton components. I expressed these thoughts here. This will remove many of the complexities we face when working with dependency injection, and even exposes SOLID principles violation even more quickly than DI by itself already does.

before I go ahead and make my whole project scoped

That is something I would not advice to do. You would typically see that only some of the 'leaf components' in your application are scoped (such as DbContext). Those scoped components don't depend on many other components. The components that you write yourself should typically be stateless and don't need any caching. So in case making the object graphs singleton is (not yet) an option, I would typically make as much of the object graph transient, and only those few leaf components scoped. Since transients can safely depend on scoped instances, everything will work as expected.

Steven
  • 166,672
  • 24
  • 332
  • 435
  • 1
    That's a comprehensive explanation and I think I understand - It's all about thread safety. My project is organised in a way that only singletons are ever used by multiple threads. Scoped and transients are not shared between threads. Transient is our default with 1 or 2 singletons and a handful of scoped. While the error message is frustrating right now, I can see how it could help me dodge a bullet if it had caught a case where a one of my singletons was dependent on a transient. – Twisted Oct 12 '15 at 15:10
  • I don't understand. I can have a thread-safe class as a 'transient' dependency, injected into singletons and other transient classes. The 'Lifestyle' does not necessarily need to correlate with the thread-safety. (Singleton<->thread-safe, Transient<->non-thread-safe) – Efrain Mar 22 '16 at 10:37
  • @Efrain, the point is, that transient service.will no longer be transient when injected into a singleton. Sometimes this might work, but this often gives problems and since this is a commom source of bugs, Simple Injector prevents you prom doing that. If your service is intended to live a long life: make it singleton. – Steven Mar 22 '16 at 10:47
  • Well, the injected 'transient' instance will live as long as the singleton. But why is this "a common source of bugs"? - And maybe I want to create another instance of that dependency that is injected into another transient instance - which I can't do if I make it a singleton. – Efrain Mar 22 '16 at 12:08
  • @Efrain if you want to have to different 'singletons', it means that this service has mutable state; services should be immutable. – Steven Mar 22 '16 at 14:58
  • 1
    No, I don't want different singletons - I just don't want to force my singleton's injection members to be singletons themselves. I don't see why that is necessary, or "leads to bugs". – Efrain Mar 22 '16 at 15:26
  • Well, it is a pitty that you created a restriction based just on your own world-view. Why services should be immutable? The inability to create a Singleton with a Transient object is a real limitation which, in my opinion, is a solid drawback of the container. Consider the following: suppose we have 2 singleton entities each of which uses a timer(ITimer) and we have an implementaion for ITimer(SystemTimer). And to have 2 separate timers we need to have them Transient but we can't — the container complains. – ixSci Apr 20 '16 at 09:30
  • 3
    @ixSci every library creates restrictions. And yes Simple Injector is even more opinionated than other DI libraries are. But note that there is no hard limitation that prevents you from injecting transients into singletons; it is simply the sensible default. You can [suppress these warnings](https://simpleinjector.org/suppress) or define your own [InstancePerDependencyLifestyle](https://github.com/simpleinjector/SimpleInjector/blob/master/src/SimpleInjector.CodeSamples/InstancePerDependencyLifestyle.cs). – Steven Apr 20 '16 at 10:16
  • @ixSci But consider posting a new question on SO or Github to describe your design in detail, so we can give feedback on this. There are usually better ways of designing or composing your system. – Steven Apr 20 '16 at 10:16
  • 1
    Thank you for the workaround — it helped. As to the advice to take an advice on my design: the problem is I don't think there is any issue with my design I think there is a flaw in yor reasoning about lifestyle relationships so in my opinion it is you who should change the design not me :). Still you made your choice and we(users) should either appreciate it or choose another container. – ixSci Apr 21 '16 at 07:27
  • @ixSci you should switch to a library that better suits your way of working. – Steven Apr 21 '16 at 11:01
  • I like everything about simpleinjector, except this. I see no problem at all with a singleton depending on transients. I'm grateful to have discovered this option buried in the API - [SuppressLifestyleMismatchVerifcation](https://simpleinjector.org/ReferenceLibrary/html/P_SimpleInjector_ContainerOptions_SuppressLifestyleMismatchVerification.htm) – PaulMolloy Jul 26 '18 at 10:45
  • Setting that property is a pretty bad idea, because it suppress any mismatch, also when you inject a scoped in a singleton. – Steven Jul 26 '18 at 12:06