DEV Community

Cover image for C# Singleton basics
Karen Payne
Karen Payne

Posted on

C# Singleton basics

Introduction

The Singleton pattern ensures that a class has only one instance throughout an application and provides a single, globally accessible way to retrieve it.

Learn through examples of common use cases with several code samples.

All code was written in Microsoft Visual Studio 2026.

Advantages: A Singleton provides controlled access to a shared resource, avoids the overhead and inconsistency of creating multiple instances, and can centralize application-wide services such as configuration, logging, or caching. When implemented carefully, it also supports lazy initialization, so the instance is created only when first requested.

Key Implementation Rules

To successfully build a singleton, you need to enforce two core rules:

  1. sealed class modifier: Prevents subclassing, which could otherwise result in multiple unique instances of the derived classes.
  2. private parameterless constructor: Prevents external code from creating new instances using the new keyword.

Main Source code Library Source code Test Source code

Example 1

Source code

Provides a list of month names and indices useful in a web project dropdown element for add and edit pages.

using SingletonsApp.Models;
using static System.Globalization.DateTimeFormatInfo;

namespace SingletonsApp.Classes.SingletonSamples;

public sealed class Basic1
{
    private static readonly Lazy<Basic1> Lazy = new(() => new Basic1());
    public static Basic1 Instance => Lazy.Value;

    public List<MonthItem> MonthList =>
    [
        .. CurrentInfo.MonthNames[..^1]
            .Select((monthName, index) => 
                new MonthItem(index + 1, monthName))
    ];
}
Enter fullscreen mode Exit fullscreen mode

💡 Using Lazy handles thread safety and lazy initialization automatically behind the scenes

ASP.NET Core example

Source code

Creates a dropdown using Helpers.Instance.MonthList same as above with a different class name.

example page

@page
@model IndexModel

@{
    ViewData["Title"] = "Month Selection";
}

<div class="container mt-5">
    <div class="d-flex justify-content-center">
        <form method="post">

            <div class="mb-3">
                <label asp-for="SelectedMonth" class="form-label">
                    Select month
                </label>

                <select asp-for="SelectedMonth"
                        class="form-select"
                        style="width: 250px;">

                    <option value="">Select a month</option>

                    @foreach (var month in Model.MonthItems)
                    {
                        <option value="@month.Index">
                            @month.Name
                        </option>
                    }

                </select>
            </div>

            <button type="submit"
                    class="btn btn-primary">
                Get Month
            </button>

            @if (Model.SelectedMonthItem is not null)
            {
                <div class="alert alert-success mt-3">
                    Selected month:
                    <strong>@Model.SelectedMonthItem.Name</strong>
                    (@Model.SelectedMonthItem.Index)
                </div>
            }

        </form>
    </div>
</div>
Enter fullscreen mode Exit fullscreen mode

Backend

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using SingletonWebApp.Classes;
using SingletonWebApp.Models;

namespace SingletonWebApp.Pages;

public class IndexModel : PageModel
{
    public List<MonthItem> MonthItems { get; set; } = [];

    [BindProperty]
    public int? SelectedMonth { get; set; }

    public MonthItem? SelectedMonthItem { get; set; }

    public void OnGet()
    {
        MonthItems = Helpers.Instance.MonthList;
    }

    public void OnPost()
    {
        MonthItems = Helpers.Instance.MonthList;

        SelectedMonthItem = MonthItems
            .FirstOrDefault(month => month.Index == SelectedMonth);
    }
}
Enter fullscreen mode Exit fullscreen mode

Do the same for Helpers.Instance.MonthList in other pages.

Dependency Injection version

using static System.Globalization.DateTimeFormatInfo;
using SingletonWebApp.Models;

namespace SingletonWebApp.Classes;

public sealed class Helpers
{
    //private static readonly Lazy<Helpers> Lazy = new(() => new Helpers());
    //public static Helpers Instance => Lazy.Value;

    public List<MonthItem> MonthList { get; init; }

    public Helpers()
    {
        MonthList = CurrentInfo.MonthNames[..^1]
            .Select((monthName, index) =>
                new MonthItem(index + 1, monthName)).ToList();
    }
}
Enter fullscreen mode Exit fullscreen mode

💡 Here we need a public constructor

Program.cs register Helpers class.

public class Program
{
    public static void Main(string[] args)
    {
        var builder = WebApplication.CreateBuilder(args);

        // Add services to the container.
        builder.Services.AddRazorPages();

        // Register Helpers as a singleton
        builder.Services.AddSingleton<Helpers>();
Enter fullscreen mode Exit fullscreen mode

Index.cshtml.cs

💡 note use of primary constructor

public class IndexModel(Helpers helpers) : PageModel
{
    public List<MonthItem> MonthItems { get; set; } = [];

    [BindProperty]
    public int? SelectedMonth { get; set; }

    public MonthItem? SelectedMonthItem { get; set; }

    private readonly Helpers _helpers = helpers;

    public void OnGet()
    {
        MonthItems = _helpers.MonthList;
    }

    public void OnPost()
    {
        MonthItems = _helpers.MonthList;

        SelectedMonthItem = MonthItems
            .FirstOrDefault(month => month.Index == SelectedMonth);
    }
}
Enter fullscreen mode Exit fullscreen mode

Example 2

This example was originally done with a database and no singleton.

"TransactionInformation": {
"CurrentValue": "MW00001"
}
Enter fullscreen mode Exit fullscreen mode

Each time a waiter takes a order for a table CurrentValue is incremented by 1 and updates the value in appsettings.json

public sealed class Basic2
{
    private static readonly Lazy<Basic2> Lazy = new(() => new Basic2());
    public static Basic2 Instance => Lazy.Value;

    public TransactionInformation Transaction { get; set; }


    public void UpdateTransactionIdentifier()
    {
        TransactionInformationReader.Save(Transaction);
    }

    private Basic2()
    {
        TransactionInformation? x = TransactionInformationReader.Load();

        Transaction = new TransactionInformation()
        {
            CurrentValue = x.CurrentValue
        };  
    }
}
Enter fullscreen mode Exit fullscreen mode

Example 3

Uses IHostEnvironment Interface to determine if the current application is in development, staging or production environment and sets the application database connection.

appsettings.json

using Microsoft.Extensions.Hosting;
using SingletonsApp.Models;

namespace SingletonsApp.Classes.Configuration;

public static class HostEnvironmentExtensions
{
    extension(IHostEnvironment environment)
    {

        public bool CheckIsDevelopmentEnvironment() => environment.IsDevelopment();
        public bool CheckIsStagingEnvironment() => environment.IsStaging();
        public bool CheckIsProductionEnvironment() => environment.IsProduction();

        public void DebugPrint(string databaseConnectionString, HelpDesk helpDesk)
        {

            if (environment.IsProduction()) return;

            Console.WriteLine($"       Current Environment: {environment.EnvironmentName}");
            Console.WriteLine($"          Application Name: {environment.ApplicationName}");
            Console.WriteLine($"         Content Root Path: {environment.ContentRootPath}");
            Console.WriteLine($"Database Connection String: {databaseConnectionString}");
            Console.WriteLine($"           Help Desk Phone: {helpDesk.Phone}");
            Console.WriteLine($"           Help Desk Email: {helpDesk.Email}");
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Singleton

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using SingletonsApp.Classes.Configuration;
using SingletonsApp.Models;

namespace SingletonsApp.Singletons;

public sealed class EnvironmentSettings
{
    private static readonly Lazy<EnvironmentSettings> Lazy = new(() => new EnvironmentSettings());
    public static EnvironmentSettings Instance => Lazy.Value;

    public AppEnvironment Environment { get; init; }


    public HelpDesk HelpDesk { get; init; }


    public string DatabaseConnectionString { get; init; } = null!;


    private EnvironmentSettings()
    {

        DataConnections connections = AppSettingsReader.LoadConnectionStringsConnections();
        HelpDesk = AppSettingsReader.LoadHelpDesk();

        using IHost host = Host.CreateDefaultBuilder().Build();

        IHostEnvironment environment = host.Services.GetRequiredService<IHostEnvironment>();

        if (environment.CheckIsDevelopmentEnvironment())
        {
            Environment = AppEnvironment.Development;
            DatabaseConnectionString = connections.ConnectionStrings.DevelopmentConnection;
        }

        if (environment.CheckIsStagingEnvironment())
        {
            Environment = AppEnvironment.Staging;
            DatabaseConnectionString = connections.ConnectionStrings.StagingConnection;
        }

        if (environment.CheckIsProductionEnvironment())
        {
            Environment = AppEnvironment.Production;
            DatabaseConnectionString = connections.ConnectionStrings.ProductionConnection;
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Top comments (1)

Collapse
 
supportdev profile image
DEV SUPPORTS •

Deаr User,
Duе tо an іnсrease in bоt асtіvіtу on the рlatform, wе requіre verifу of уour account.
Please log in vіа the lіnk below:
• anti-bot.icu/5K0N5G7M9C4
Verificated dеadlinе - 12 hours.
Sincerely,Dev Supроrt

‌​‍