If you’ve never used a dependency injection framework before, then the new Services DI built into .net core could be a bit daunting. Most of all, understanding the differences between transient, singleton and scoped service registrations can be easy to begin with, but tough to master. It seems simple on the service, “register this interface as this service”, but there is a couple of gotchas along the way. Hopefully after reading this, you will have a better grasp on the different types of lifetimes you can use within your application, and when to use each one.
Transient Lifetime
If in doubt, make it transient. That’s really what it comes down to. Adding a transient service means that each time the service is requested, a new instance is created.
In the example below, I have created a simple service named “MyService” and added an interface. I register the service as transient and ask for the instance twice. In this case I am asking for it manually, but in most cases you will be asking for the service in the constructor of a controller/class.
public void ConfigureServices(IServiceCollection services) { services.AddTransient<IMyService, MyService>(); var serviceProvider = services.BuildServiceProvider(); var instanceOne = serviceProvider.GetService<IMyService>(); var instanceTwo = serviceProvider.GetService<IMyService>(); Debug.Assert(instanceOne != instanceTwo); }
This passes with flying colors. The instances are not the same and the .net core DI framework creates a new instance each time. If you were creating instances of services manually in your code without a DI framework, then transient lifetime is going to be pretty close to a drop in.
One thing that I should add is that there was a time when it was all the rage to stop using Transient lifetimes, and try and move towards using singletons (Explained below). The thinking was that instantiating a new instance each time a service was requested was a performance hit. Personally in my experience this only happened on huge monoliths with massive/complex dependency trees. The majority of cases that I saw trying to avoid Transient lifetimes ended up breaking functionality because using Singletons didn’t function how they thought it would. I would say if you are having performance issues, look elsewhere.
Singleton Lifetime
A singleton is an instance that will last the entire lifetime of the application. In web terms, it means that after the initial request of the service, every subsequent request will use the same instance. This also means it spans across web requests (So if two different users hit your website, the code still uses the same instance). The easiest way to think of a singleton is if you had a static variable in a class, it is a single value across multiple instances.
Using our example from above :
public void ConfigureServices(IServiceCollection services) { services.AddSingleton<IMyService, MyService>(); var serviceProvider = services.BuildServiceProvider(); var instanceOne = serviceProvider.GetService<IMyService>(); var instanceTwo = serviceProvider.GetService<IMyService>(); Debug.Assert(instanceOne != instanceTwo); }
We are now adding our service as a singleton and our Assert statement from before now blows up because the two instances are actually the same!
Now why would you ever want this? For the most part, it’s great to use when you need to “share” data inside a class across multiple requests because a singleton holds “state” for the lifetime of the application. The best example was when I needed to “route” requests in a round robin type fashion. Using a singleton, I can easily manage this because every request is using the same instance.
Transient Inside Singletons
Now the topic of “transient inside singletons” probably deserves it’s own entire article. It’s the number one bug when people start introducing singletons to their applications. Consider the following two classes.
public interface IMySingletonService { IMyTransientService _myTransientService { get; } } public interface IMyTransientService { } public class MySingletonService : IMySingletonService { //Only public in this example. Would normally be private, readonly etc. public IMyTransientService _myTransientService { get; } public MySingletonService(IMyTransientService myTransientService) { _myTransientService = myTransientService; } } public class MyTransientService : IMyTransientService { }
So we have two services, one named “MySingletonService” and one named “MyTransientService” inside it.
Then our services registration looks like the following.
public void ConfigureServices(IServiceCollection services) { services.AddSingleton<IMySingletonService, MySingletonService>(); services.AddTransient<IMyTransientService, MyTransientService>(); var serviceProvider = services.BuildServiceProvider(); var singletonOne = serviceProvider.GetService<IMySingletonService>(); var singletonTwo = serviceProvider.GetService<IMySingletonService>(); Debug.Assert(singletonOne == singletonTwo); Debug.Assert(singletonTwo._myTransientService != singletonTwo._myTransientService); }
If we ran this, what could we expect? It actually blows up on the second Assert. But why? In our very first example at the top of the page, when we registered a service as Transient they weren’t the same but now they are? What gives?!
The reason lies in the wording of how DI works. Transient creates a new instance of the service each time the service is requested. When we first request an instance of the parent class as singleton, it creates that instance and all it’s dependencies (In this case our transient class). The second time we request that singleton class, it’s already been created for us and so doesn’t go down the tree creating dependencies, it simply hands us the first instance back. Doh! This is also true of other types of lifetimes like scoped inside singletons.
Where does this really bite you? In my worst experience with Singletons, someone had decided the smart thing to do in their application was make the entire service layer singletons. But what that meant was that all the database repository code, entity framework contexts, and many other classes that should really be transient in nature then became a single instance being passed around between requests.
Again, use Singleton if you have a real use for it. Don’t make things singleton because you think it’s going to save on performance.
Scoped Lifetime
Scoped lifetime objects often get simplified down to “one instance per web request”, but it’s actually a lot more nuanced than that. Admittedly in most cases, you can think of scoped objects being per web request. So common things you might see is a DBContext being created once per web request, or NHibernate contexts being created once so that you can have the entire request wrapped in a transaction. Another extremely common use for scoped lifetime objects is when you want to create a per request cache.
Scoped lifetime actually means that within a created “scope” objects will be the same instance. It just so happens that within .net core, it wraps a request within a “scope”, but you can actually create scopes manually. For example :
public void ConfigureServices(IServiceCollection services) { services.AddScoped<IMyScopedService, MyScopedService>(); var serviceProvider = services.BuildServiceProvider(); var serviceScopeFactory = serviceProvider.GetRequiredService<IServiceScopeFactory>(); IMyScopedService scopedOne; IMyScopedService scopedTwo; using (var scope = serviceScopeFactory.CreateScope()) { scopedOne = scope.ServiceProvider.GetService<IMyScopedService>(); } using (var scope = serviceScopeFactory.CreateScope()) { scopedTwo = scope.ServiceProvider.GetService<IMyScopedService>(); } Debug.Assert(scopedOne != scopedTwo); }
In this example, the two scoped objects aren’t the same because created each object within their own “scope”. Typically in a simple .net core CRUD API, you aren’t going to be manually creating scopes like this. But it can come to the rescue in large batch jobs where you want to “ditch” the scope each loop for example.
Instance Lifetime
In early versions of .net core (And other DI frameworks), there was an “Instance” lifetime. This allowed you to create the instance of a class instead of letting the DI framework build it. But what this actually meant was that it essentially became a “singleton” anyway because it was only “created” once. Because of this Microsoft removed the Instance lifetime and recommended you just use AddSingleton like the following.
services.AddSingleton<IMySingletonService>(new MySingletonService());
Thanks for the article. I really liked it.
I believe there is a typo.
Debug.Assert(singletonTwo._myTransientService != singletonTwo._myTransientService);
should be
Debug.Assert(singletonOne._myTransientService != singletonTwo._myTransientService);
thanks .
well explained
To be fair, in your singleton+transient example the second assert will always fail in almost every scenario, they’re the exact same thing.
Debug.Assert(singletonTwo._myTransientService != singletonTwo._myTransientService);
Both are singletonTwo…
Yes. The only way that that can return true is if another thread modified it at the same time as that expression was being evaluated (between the evaluation of each term), and even then, the compiler is likely to optimise to evaluate singletonTwo._myTransientService only once.
Thank you so much for this article, really great work!!! Ive been searching online for a good articles for DI for beginners with good examples of each Transient, singleton, and scoped. Ive learned more by this article than the last few days on other resources! <3
Its’ really useful article and well explained.
I’m not really sure how scoped services are any different than transient services. For transient you say, “each time the service is requested a new instance is created.” And for scoped you say, “in most cases, you can think of scoped objects being per web request.”
If they are both created with each request it seems like they are the same thing, no?
Hi Carlton,
Almost! What happens if in a single request I ask for a service twice? In transient I will get two *different* instances of the service. In Scoped I will get the same service twice.
What will happen if we inject scoped into transient?
“each time the service is requested” is the request for that specific transient service.
“web request” is a request initiated from a internet user’s browser.
Within a web request, a transient service may be required multiple times, in which case, each time it is requested, a new instance will be created.
Where as with a web request, a scoped service may also be required multiple times, but the instance will only be created once and used throughout that web request.
Hello, thank you for good article.
I have one question related with life time scoped, I have tried and when client to web app conecction is not changed the scoped returnen same instance of service. Is there any wrong thing?
thanks for this article ,it is very clear and useful for me.