C#
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C#
-
Section 4: Working with Data
-
Section 5: Error Handling
-
Section 6: Delegates and Events
-
Section 7: Async Programming
-
Section 8: More Language Features
-
Section 9: File I/O and Serialization
-
Section 10: Networking in .NET
-
Section 11: The .NET Ecosystem
-
Section 12: Memory and Performance
-
Section 13: Concurrency Beyond Async
-
Section 14: Reflection and Attributes
-
Section 15: Testing and Best Practices
-
Section 16: Design Patterns in C#
-
Section 17: Standard Library Deep Dive
-
Section 18: Data Structures and Algorithms in C#
-
Section 19: GUI and Desktop Development Overview
-
Section 20: Practical Projects
-
Section 21: More Practice Exercises
-
Section 22: More Standard Library and Text Processing
-
Section 23: More Design Patterns
-
Section 24: More Projects
-
Section 25: Interview Practice
-
Section 26: C# Keywords Reference (Modifiers)
-
Section 27: C# Keywords Reference (Statements)
-
Section 28: C# Keywords Reference (Operators)
-
Section 29: BCL: System.Collections.Generic
-
Section 30: BCL: System.Linq
-
Section 31: BCL: System.Threading
-
Section 32: BCL: System.IO
-
Section 33: BCL: System.Text and System.Text.Json
-
Section 34: BCL: System.Net.Http
-
Section 35: C# Language Specification Topics
-
Section 36: More Practice Exercises
-
Section 37: More Async Patterns
-
Section 38: More BCL: System.Reflection and System.Diagnostics
-
Section 39: Nullable Reference Types In Depth
-
Section 40: C# Records and Pattern Matching In Depth
-
Section 41: Dependency Injection Deep Dive
-
Section 42: More Interview and Whiteboard Practice
237: Service Lifetimes: Transient, Scoped, Singleton
If you've been working with Dependency Injection (DI) in .NET, you've probably noticed that when you register a service in Program.cs, you have to pick one of three methods: AddTransient, AddScoped, or AddSingleton. If you're just starting out, they might feel interchangeable, but picking the wrong one can lead to some truly bizarre bugs—like data leaking between users or memory leaks that crash your server.
To wrap your head around this, imagine you're running a coffee shop. Think about the things your staff interacts with throughout the day:
- The Disposable Cup (Transient): Every time a customer orders a drink, the barista grabs a brand new paper cup. Once that drink is gone, the cup is tossed. You never reuse a cup for a second order.
- The Cafe Table (Scoped): When a customer walks in, they take a table. For the duration of their "visit" (their request), they use that same table for their coffee, their muffin, and their laptop. But once they leave, that table is cleared and is no longer associated with them.
- The Espresso Machine (Singleton): There is only one high-end espresso machine in the whole shop. Every single customer, every single barista, and every single order uses that one specific machine from the moment the shop opens until it closes at night.
Fresh Every Time: Transient
In C#, a Transient service is exactly like that paper cup. The DI container creates a brand new instance of the service every single time it's requested. If you inject a transient service into three different classes in the same HTTP request, you'll get three different objects.
builder.Services.AddTransient<IGuidGenerator, GuidGenerator>();
I usually use Transient services for lightweight, stateless components. If the class doesn't hold onto any data and just performs a quick calculation or a transformation, Transient is the safest bet because you don't have to worry about multi-threading issues or "stale" data.
One Per Request: Scoped
A Scoped service is the "table" of your application. In a web app, a "scope" is typically a single HTTP request. The first time a service is requested during a web request, .NET creates it. Every other class that asks for that same service within that same request gets the exact same instance.
builder.Services.AddScoped<IUserSession, UserSession>();
This is incredibly powerful for things like database contexts (DbContext) or user session data. You want your Repository and your Service layer to share the same database connection during a single request so you can use a single transaction. If you used Transient here, you'd be opening and closing database connections constantly, which would kill your performance.
The Single Source of Truth: Singleton
A Singleton is the espresso machine. It is created the first time it's requested (or when the app starts) and it lives until the process terminates. Every single user and every single request shares this one instance.
builder.Services.AddSingleton<ICacheService, CacheService>();
Singletons are great for configuration settings or in-memory caches. However, be careful. Because everyone shares the same instance, you have to make sure the class is thread-safe. If two different users try to update a list inside a Singleton at the exact same millisecond, you're going to have a bad time unless you've implemented proper locking.
The Captive Dependency Trap
Here is a pro tip that will save you hours of debugging: Never inject a Scoped service into a Singleton service.
I call this the "Captive Dependency." If a Singleton (which lives forever) takes a Scoped service (which is supposed to die after the request) in its constructor, that Scoped service is now "captured." It will be held in memory for the life of the application, effectively turning it into a Singleton. This often leads to database connection crashes because the DbContext was never disposed of.
📋 Practical Task
Build a Lifetime Correlation Tracker
To see these lifetimes in action, you're going to build a small diagnostic tool. Create a simple ASP.NET Core Web API project and implement the following:
- Create an interface
ILifetimeIdwith a single property:Guid Id { get; }. - Create three separate classes that implement this interface:
TransientId,ScopedId, andSingletonId. Each should assign aGuid.NewGuid()to theIdproperty in its constructor. - Register these three classes in
Program.csusing their respective lifetimes (AddTransient,AddScoped, andAddSingleton). - Create a service called
LifetimeDemoServicethat takes all threeILifetimeIdimplementations in its constructor. Give it a method that returns the IDs as a string. - Create a Controller with an endpoint that injects both the
LifetimeDemoServiceand the threeILifetimeIdservices directly. - The endpoint should return a JSON object showing the IDs from the service AND the IDs injected directly into the controller.
The Goal: Refresh your browser page several times. Observe which IDs change on every refresh, which IDs stay the same within one request but change on the next, and which ID never changes no matter how many times you refresh or restart the request (but changes when you restart the app).
There are no comments for now.