0
0
CsharpComparisonBeginner · 4 min read

Singleton vs Scoped vs Transient in C#: Key Differences and Usage

In C#, Singleton creates one instance for the entire application lifetime, Scoped creates one instance per client request or scope, and Transient creates a new instance every time it is requested. These lifetimes control how objects are shared and reused in dependency injection.
⚖️

Quick Comparison

Here is a quick table comparing Singleton, Scoped, and Transient lifetimes in C# dependency injection.

AspectSingletonScopedTransient
Instance CountOne instance for entire appOne instance per scope/requestNew instance every time
LifetimeApplication lifetimeScope lifetime (e.g., HTTP request)Very short, per injection
Use CaseShared services, configPer user/request dataLightweight, stateless services
PerformanceBest for expensive objectsModerateCan be costly if overused
StateShared state across appState isolated per scopeNo shared state
ExampleLogging serviceDatabase contextEmail sender
⚖️

Key Differences

Singleton means the service is created once and reused everywhere. It lives as long as the application runs, so all parts of the app share the same instance. This is good for services that hold shared data or are expensive to create.

Scoped creates a new instance for each scope, commonly an HTTP request in web apps. This means each user request gets its own instance, preventing data leaks between requests. Scoped services are useful for things like database contexts where you want to track changes per request.

Transient creates a new instance every time the service is requested. This is useful for lightweight, stateless services where you want a fresh object each time. However, overusing transient services can hurt performance because of frequent object creation.

⚖️

Code Comparison

Here is how you register and use a Singleton service in C# with dependency injection.

csharp
public interface ILoggerService
{
    void Log(string message);
}

public class LoggerService : ILoggerService
{
    public LoggerService()
    {
        Console.WriteLine("LoggerService created");
    }

    public void Log(string message)
    {
        Console.WriteLine($"Log: {message}");
    }
}

// In Startup.cs or Program.cs
var services = new ServiceCollection();
services.AddSingleton<ILoggerService, LoggerService>();

var provider = services.BuildServiceProvider();

var logger1 = provider.GetService<ILoggerService>();
var logger2 = provider.GetService<ILoggerService>();

logger1.Log("First message");
logger2.Log("Second message");

Console.WriteLine(Object.ReferenceEquals(logger1, logger2));
Output
LoggerService created Log: First message Log: Second message True
↔️

Scoped Equivalent

Here is how you register and use a Scoped service in C# with dependency injection.

csharp
public interface IDbContext
{
    Guid GetOperationId();
}

public class DbContext : IDbContext
{
    private Guid _operationId;

    public DbContext()
    {
        _operationId = Guid.NewGuid();
        Console.WriteLine($"DbContext created with ID: {_operationId}");
    }

    public Guid GetOperationId() => _operationId;
}

// In Startup.cs or Program.cs
var services = new ServiceCollection();
services.AddScoped<IDbContext, DbContext>();

var provider = services.BuildServiceProvider();

using (var scope1 = provider.CreateScope())
{
    var db1 = scope1.ServiceProvider.GetService<IDbContext>();
    var db2 = scope1.ServiceProvider.GetService<IDbContext>();
    Console.WriteLine(db1.GetOperationId() == db2.GetOperationId()); // True
}

using (var scope2 = provider.CreateScope())
{
    var db3 = scope2.ServiceProvider.GetService<IDbContext>();
    Console.WriteLine(db3.GetOperationId());
}
Output
DbContext created with ID: 3f1e2a4b-... (some GUID) True DbContext created with ID: 7a9c8d2f-... (different GUID)
🎯

When to Use Which

Choose Singleton when you need one shared instance for the whole app, like logging or configuration services that are expensive to create and safe to share.

Choose Scoped when you want a new instance per user request or operation, such as database contexts that track changes per request and should not be shared across requests.

Choose Transient when you want a new instance every time, ideal for lightweight, stateless services that do not hold data and are cheap to create.

Key Takeaways

Singleton creates one instance for the entire app lifetime and shares it everywhere.
Scoped creates one instance per scope, typically per web request, isolating data per user.
Transient creates a new instance every time it is requested, suitable for stateless services.
Use Singleton for shared, expensive services; Scoped for per-request data; Transient for lightweight, short-lived services.