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.
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
🧠 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.