1

I am using Simple Injector for my DI library. I have controllers in my asp.net MVC site that take services in their constructors via this library. When I look at the Diagnostic Tools in Visual Studio and see my Managed Memory I see multiple instances of the same service.

var container = new Container();
container.Options.DefaultScopedLifestyle = new WebRequestLifestyle();
    
container.RegisterWebApiControllers(GlobalConfiguration.Configuration);
container.RegisterMvcControllers(Assembly.GetExecutingAssembly());
container.RegisterMvcIntegratedFilterProvider();

RegisterComponents(container);
    
container.Verify();
DependencyResolver.SetResolver(new SimpleInjectorDependencyResolver(container));

My question is, is this by design. I figured one IPaymentsService would be used throughout all the controllers but I have a count of 187? I would think it should be 1.

I am thinking about adding the below line. Seems to be working fine, and now I see 700,000 KB less of memory used and a 10+ second faster load time on the site. Is there any downside with this?

container.Options.DefaultLifestyle = Lifestyle.Scoped;

enter image description here

Steven
  • 166,672
  • 24
  • 332
  • 435
Mike Flynn
  • 22,342
  • 54
  • 182
  • 341
  • I guess it depends on how you have configured the container for those services. – DavidG May 11 '21 at 14:55
  • I am just useing the normal DI injection, nothing special, no singleton etc – Mike Flynn May 11 '21 at 14:56
  • If its not a singleton, why would you only expect a single instance then? – DavidG May 11 '21 at 14:58
  • I dont know, that is what I am asking. Should I make these singletons? Is that a good idea for performance? These do not hold state. – Mike Flynn May 11 '21 at 14:59
  • 2
    If you don't know what it means, you need to go read up on your container lifetimes right now or you're going to end in a real mess. – DavidG May 11 '21 at 15:00
  • I do know what it means, but is that a best practice for DI? I only do singletons for special services, not all my hundreds of services. But it looks like I should do this for all of them. – Mike Flynn May 11 '21 at 15:01
  • 1
    Since you are saying that, you really don't know what it means. – DavidG May 11 '21 at 15:01
  • It seems to me that I shoul make most of my business services scoped instead of the default transient. 193 count of AAUService is crazy and not needed. It calls stateless methods and within a scoped request one is fine per user. Does that make sense? – Mike Flynn May 12 '21 at 21:24
  • 2
    Lifetime management is the most difficult part when using DI. I therefore cannot emphasize enough how important the comment of @DavidG is. Please read up on lifetime management which can be found [here](https://docs.simpleinjector.org/en/latest/lifetimes.html) in the case of Simple Injector. I would however also advise to understand the differences by reading for example this [book](https://www.manning.com/books/dependency-injection-principles-practices-patterns) – Ric .Net May 12 '21 at 22:41
  • I understand that but look at this post. Event with simple language there isnt a great answer to what I am asking, others are still up in the air in the comments https://stackoverflow.com/questions/38138100/addtransient-addscoped-and-addsingleton-services-differences/64776798#64776798 – Mike Flynn May 12 '21 at 22:53
  • But you're *not* understanding the different life cycles. If you want a single instance of a service (and it is stateless) then you need a singleton. – DavidG May 12 '21 at 23:03
  • I really don’t want singletons everywhere in memory so wouldn’t scoped be a better option for releasing memory back. – Mike Flynn May 12 '21 at 23:12

1 Answers1

1

Let me start with the basics, most of which you will likely be familiar with, but let’s do it for completeness and correctness.

  • With Simple Injector, Transient means short-lived and not cached. If service X is registered as Transient, and multiple components depend on X, each get their own new instance. Even if X implements IDisposable, it is not tracked nor disposed of. After creation, Simple Injector forgets about Transients immediately.
  • Within a specifically defined scope (e.g. a web request), Simple Injector will only create a single instance of a service registered as Scoped. If service X is Scoped, and multiple components depend on X, all components that are created within that same scope get the same instance of X. Scoped instances are tracked and if they implement IDisposable (or IAsyncDisposable), Simple Injector calls their Dispose method when the scope is disposed of. In the context of a web request disposal of scopes (and therefore Scoped components) this managed for you by the Simple Injector integration packages.
  • With Singleton, Simple injector will ensure at most one instance of that service within a single Container instances. If you have multiple Container instances (which you typically wouldn’t in production, but more likely during testing) each Container instance gets its own cache with Singletons. The described behavior is specific to Simple Injector. Other DI Containers might have different behavior and definitions when it comes to these lifestyles:
  • Simple Injector considers a Transient component short lived, possibly including state, while the ASP.NET Core DI (MS.DI) considers Transient components to be *stateless, living for any possible duration. * Because of this different view, with MS.DI, Transients can be injected into Singletons, while with Simple Injector you can’t. Simple Injector will give an diagnostic error when you call Verify().
  • Transients are not disposed of by Simple Injector. That’s why you get a diagnostic error when you register a Transient that implements IDisposable. Again, this is very different with some other DI Containers. With MS.DI, transients are tracked and disposed of when their scope, from which they are resolved, is disposed of. There are pros and cons to this. Important con is that this will lead to accidental memory leaks when resolving disposable transients from the root container, because MS.DI will store those Transients forever.

About choosing the right lifestyle for a component:

  • Choosing the correct lifestyle (Transient/Scoped/Singleton) for a component is a delegate matter.
  • If a component contains state that should be reused throughout the whole application, you might want to register that component as Singleton -or- move the state out of the component and hide it behind a service which component can be registered as Singleton.
  • The expected lifetime of a component should never exceed that of its consumers. Transient is the shortest lifestyle, while Singleton is the longest. This means that a Singleton component should only depend on other Singletons, while Transient components can depend on both Transient, Scoped, and Singleton components. Scoped components should typically only have Scoped and Singleton dependencies.
  • The previous rule, however, is quite strict, which is why with Simple Injector v5 we decided to allow Scoped components take a dependency on Transient components as well. This will typically be fine in case scopes live for a short period of time (as is with web requests in web applications). If, however, you have long-running operations that operate within a single scope, but do regularly call back into the container to resolve new instances, having Scoped instances depend on (stateful) Transients could certainly cause trouble; that’s, however, not a very common use case.
  • Failing to adhere to the rule of “components should only depend on equal or longer-lived components,” results in Captive Dependencies. Simple Injector calls them “Lifestyle Mismatches,” but it’s the same thing.
  • A stateless component with no dependencies can be registered as Singleton.
  • The lifestyle of a stateless component with dependencies, depends on the lifestyle of its dependencies. If it has components that are either Scoped or Transient, it should itself be either Scoped or Transient. If it were registered as Singleton, that would lead to its dependencies becoming Captive Dependencies.
  • If a stateless component only has Singleton dependencies, it can be registered as Singleton as well.

When it comes to selecting the correct lifestyles for your components in the application, there are two basic Composition Models to choose from, namely the Ambient Composition Model and the Closure Composition Model. I wrote a series of five blog posts on this starting here. With the Ambient Composition Model you’ll make all components in your application stateless and store state outside of the object graphs. This allows you to register almost all your components as Singleton, but it does lead to complications and likely a somewhat different design of your application.

It's, therefore, more likely that you are applying the second Composition Model: The Closure Composition Model. This is the most common model, and used by most developers and pushed by most application frameworks (e.g. ASP.NET Core DI). With the Closure Composition Model, you would typically register most of your components as Transient. Only the few components in your application that do contain state would be registered as either Scoped or Singleton. Although you could certainly “tune” composition by looking at the consumers and dependencies of components and decide to increase the lifestyle (to Scoped or even Singleton) to prevent unneeded creation of instances, downside of this is that is more fragile.

For instance, if you have a stateless component X that depends on a Singleton component Y, you can make component X a Singleton as well. But once Y requires a Scoped or Transient dependency of its own, you will not only have to adjust the lifestyle of Y, but of X as well. This could cascade up the dependency chain. So instead, with the Closure Composition Model, it would typically be normal to keep things “transient unless.”

About performance:

Simple Injector is highly optimized, and it would typically not make much difference if a few extra components are created. Especially if they are stateless. When running a 32 bits process, such class would consume “8 + (4 * number-of-dependencies)” bytes of memory. In other words, a stateless component with 1 dependency consumes 12 bytes of memory, while a component with 5 dependencies consumes 28 bytes (assuming a 32 bits processes; multiply this by 2 under 64 bits).

On the other hand, managing and composing Scoped instances comes with its own overhead. Although Simple Injector is highly tuned in this regard, Scoped instances need to be cached and resolved from the scope’s internal dictionary. This comes at a cost. This means that creating a component with no dependencies a few times as Transient in a graph is likely faster than having it resolved as Scoped.

Under normal conditions, you wouldn’t have to worry about the amount of extra memory and the amount of extra CPU it takes to produce those extra Transient instances. But perhaps you are not under normal conditions. The following abnormal conditions could cause trouble:

  • If you violate the simple-injection-constructors rule: When a component’s constructor does more than simply storing its supplied dependencies (for instance calling them, doing I/O or something CPU intensive or memory intensive) creating extra transient instances can hurt a lot. You should absolutely try to stay away from this situation whenever possible.
  • **Your application creates massive object graphs: ** If object graphs are really big, you’ll likely see certain components being depended upon multiple (or even many) times in the graph. If the graph is massive (thousands of individual instances), this could lead to the creation of hundreds or even thousands of extra objects, especially when those components have Transient dependencies of their own. This situation often happens when components have many dependencies. If for instance your application’s components regularly have more than 5 dependencies, you’ll quickly see the size of the object graph explode. Important to note here is that this is typically caused by a violation of the Single Responsibility Principle. Components get many dependencies when they are doing too much, taking too many responsibilities. This easily causes them to have many dependencies, and when their dependencies have many dependencies, things can explode quite easily. The real solution in that case is to make your components smaller. For instance, if you have classes like “OrderService” and “CustomerService”, they will likely have a hodgepodge of functionality and a big list of dependencies. This causes a myriad of problems; big object graphs being one of them. Fixing this in an existing application, however, is typically not easy; it requires a different design and a different mindset.

In these kinds of scenarios changing a component’s lifestyle can be beneficial for the performance of the application. You already seem to have established this in your application. In general, changing the lifestyle from Transient to Scoped is a pretty safe change. This is why Simple Injector v5 doesn’t complain anymore when you inject Transient dependencies into Scoped consumers.

This will not be the case, however, when you have a stateful Transient component, while each consumer does expect to get its own state; in that case, changing it to Scope would in fact break your application. However, this is not a design that I typically endorse. Although I’ve seen this type of composition a few times in the past, I never do this in my applications; IMO it leads to unneeded complexity.

TLDR;

Long story short, there are a lot of factors to consider, and perhaps a lot of places in the application where the design could be approved, but in general (especially in the context of web requests) changing the lifestyle of stateless components from Transient to Scoped is usually pretty safe. If this results in a big performance win in your application, you can certainly consider making Scoped the default lifestyle.

Steven
  • 166,672
  • 24
  • 332
  • 435