Advertisement
Intermediate C# Lesson 9 of 10

Lesson 9: Static Members & Singleton Pattern in C#

Static members belong to the class, not to instances. Static constructors run once per class to initialize static state. The Singleton pattern ensures a class has exactly one instance, providing a global point of access to a shared resource.

Advertisement

Static Fields & Methods

Static fields and methods are shared across all instances of a class. They are accessed via the class name, not an instance.

public class BankAccount
{
    private static decimal _totalDeposits = 0;
    private decimal _balance = 0;

    public static decimal TotalDeposits
    {
        get { return _totalDeposits; }
    }

    public void Deposit(decimal amount)
    {
        _balance += amount;
        _totalDeposits += amount;
    }
}

// Usage
BankAccount acc1 = new BankAccount();
BankAccount acc2 = new BankAccount();

acc1.Deposit(1000);
acc2.Deposit(500);

Console.WriteLine(BankAccount.TotalDeposits); // 1500

Static Constructors

A static constructor runs once before any instance is created. It initializes static fields.

public class Configuration
{
    public static string ConnectionString { get; private set; }
    public static int MaxConnections { get; private set; }

    // Static constructor — runs once per class
    static Configuration()
    {
        ConnectionString = "Server=localhost;Database=MyApp";
        MaxConnections = 100;
        Console.WriteLine("Configuration initialized");
    }

    // Instance constructor
    public Configuration()
    {
        Console.WriteLine("Instance created");
    }
}

// Usage
var cfg1 = new Configuration();  // Output: Configuration initialized, Instance created
var cfg2 = new Configuration();  // Output: Instance created (static ran once)

The Singleton Pattern

A Singleton ensures only one instance of a class ever exists. The instance is created lazily on first access and cached for reuse.

public class Logger
{
    private static Logger _instance;

    private Logger()
    {
        // Private constructor prevents outside instantiation
    }

    public static Logger GetInstance()
    {
        if (_instance == null)
        {
            _instance = new Logger();
        }
        return _instance;
    }

    public void Log(string message)
    {
        Console.WriteLine($"[LOG] {message}");
    }
}

// Usage
Logger log1 = Logger.GetInstance();
Logger log2 = Logger.GetInstance();

Console.WriteLine(ReferenceEquals(log1, log2)); // true

Thread-Safe Singleton with Lazy<T>

For thread-safe lazy initialization, use Lazy<T> to avoid double-locking issues.

public class DatabaseConnection
{
    private static readonly Lazy<DatabaseConnection> _instance =
        new Lazy<DatabaseConnection>(() => new DatabaseConnection());

    public static DatabaseConnection Instance
    {
        get { return _instance.Value; }
    }

    private DatabaseConnection()
    {
        Console.WriteLine("Database connection created");
    }

    public void Query(string sql)
    {
        Console.WriteLine($"Executing: {sql}");
    }
}

// Usage
var db1 = DatabaseConnection.Instance;
var db2 = DatabaseConnection.Instance;

Console.WriteLine(ReferenceEquals(db1, db2)); // true
Advertisement

🧠 Quick Check — Lesson 9

What is the primary purpose of the Singleton pattern?

When to Use Static Members

Use static members for:

  • Shared counters (e.g., total objects created)
  • Configuration constants
  • Utility methods that don't need instance state
  • Factory methods

Singleton Use Cases

Singletons are appropriate for:

  • Database connections (manage one active connection pool)
  • Loggers (centralize logging)
  • Configuration managers (one global config)
  • Thread pools and thread-safe resource managers

Lesson Summary

✅

Static members belong to the class, not instances, and are shared across all objects.

✅

Static constructors run once per class to initialize shared state.

✅

The Singleton pattern guarantees one instance and provides a global access point.

✅

Use Lazy<T> for thread-safe Singleton initialization in production code.

Up Next

Lesson 10: Real-World OOP Project

Final Lesson →