Dependency Injection in .NET
Reason
The reason for writing this is that lately, I see more and more people who are using lifetime scopes more or less randomly, or use a specific one because “it is used like this everywhere in the app”. I will try to remove a little bit of the blur that people have in their eyes, think about what they need, and then start typing.
What is DI in short?
DI is the concept that promotes modular components within your app, making the code testable and maintainable (most of the time).
As a service lifetime, you’ve got AddSingleton, AddTransient and AddScoped. Each of them defines how much time your “thing” will be kept in memory and how it will behave when injected into other “things”.
Let’s take them one by one
AddSingleton
Definition: One instance is created and shared across the entire app, and it persists for the entire lifetime of the app (so until so stop-start it, or restart it).
When to use it? Well, it’s simple: You can think of this as things that are stored globally not changing at all.
Pros
- Memory efficient — You’ve for only one instance after all
- No need to instantiate and instantiate again, so no overhead in that
- Perfect for configuration data or caching
Cons
- Requires a thread-safe management
- Long lifetime can lead to memory bloat (the use of more memory than is needed)
AddScoped
Definition: A single instance is created per request (scope) in web apps, meaning all code handling a specific request will use the same instance.
When to use it? When you’ve got services that should do their job and not be persisted after the request is done and the response was given. You can think of using them to perform stateful operations within a single request.
Pros
- Optimized for state-specific actions
- Reduced memory use compared to Transient
Cons
- Limited to request scope; cannot be reused
- Cause issues if reused outside scope
AddTransient
Definition: A new instance of the service is created each time it is injected or requested.
When to use it? Stateless and lightweight services, such as utility services (if there are still a thing) or services that have minimal initialization cost.
Pros
- Isolated instances for each request
- No need for thread safety
- Ideal for short-living data
Cons
- Higher memory consumption if they are heavily used (which may also impact your performance
- Initialization overhead on each request
Let’s create chaos — Let’s mix them!
Transient in Singleton — Each time the singleton service requests the transient service, a new instance is created. Memory usage will grow if the singleton frequently requests the transient, and there can be thread-safety issues since multiple threads could call the transient in parallel.
Scoped in Singleton — This is the best! It can lead to scope leakage where scoped instances are retained beyond their intended lifetime. If a singleton service attempts to use a scoped service outside the request scope, it will either fail or retain outdated request-specific data. The bugs that will appear because of this will keep you awake for multiple nights!
Singleton in Transient — The transient service will share the same instance of the singleton. This typically works well, as the singleton remains consistent across requests. However, it’s essential to be sure that any data in the singleton is not request-specific.
Conclusion
AddTransient: Use for lightweight, stateless services. Works well in both Web apps and Azure Functions.
AddScoped: Ideal for services that need request-level isolation web apps. I would avoid them in Azure Functions.
AddSingleton: Suitable for configuration, caching, or shared resources. Be careful about memory use and thread safety.
DI is a good tool to use, but this doesn’t mean you need to apply it everywhere! Choosing carefully the appropriate lifetime can have a big impact on your application performance, scalability, and behaviour.
Also, if you are curious about how things are done in Azure functions you can find another article I wrote specifically for them, here. There you’ll find information on how the performance is impacted, scalability, state management and much more.