Quick Overview
| Aspect | Static Class | Singleton |
|---|
| Instance | No instance created | One instance created |
| Memory | Loaded at app start | Created when first accessed |
| Inheritance | Cannot inherit/be inherited | Can inherit and be inherited |
| Interface | Cannot implement interfaces | Can implement interfaces |
| Thread Safety | Thread-safe by default | Requires manual thread safety |
| Testing | Hard to mock/test | Can be mocked/tested |
| Polymorphism | No polymorphism | Supports polymorphism |
1. Static Class π
Definition:
A static class is a class that cannot be instantiated and contains only static members.
β
Code Example:
public static class MathHelper{
public static double PI = 3.14159;
public static double CalculateArea(double radius)
{
return PI * radius * radius;
}
public static double CalculateCircumference(double radius)
{
return 2 * PI * radius;
}}
// Usagedouble area = MathHelper.CalculateArea(5.0);double circumference = MathHelper.CalculateCircumference(5.0);
Characteristics:
β
No instantiation - Cannot create objects
β
Compile-time binding - Method calls resolved at compile time
β
Memory efficient - Loaded once in memory
β
Thread-safe - No shared instance state
β No inheritance - Cannot inherit from or be inherited
β No interfaces - Cannot implement interfaces
β Hard to test - Cannot mock static methods easily
2. Singleton Pattern π―
Definition:
Singleton ensures a class has only one instance and provides global access to that instance.
β
Code Example (Thread-Safe):
public class DatabaseConnection{
private static DatabaseConnection _instance;
private static readonly object _lock = new object();
// Private constructor prevents external instantiation
private DatabaseConnection()
{
ConnectionString = "Server=localhost;Database=MyDB;";
}
public static DatabaseConnection Instance
{
get
{
if (_instance == null)
{
lock (_lock)
{
if (_instance == null)
_instance = new DatabaseConnection();
}
}
return _instance;
}
}
public string ConnectionString { get; private set; }
public void ExecuteQuery(string query)
{
Console.WriteLine($"Executing: {query}");
}}
// UsageDatabaseConnection db = DatabaseConnection.Instance;db.ExecuteQuery("SELECT * FROM Users");
Modern C# Singleton (Lazy):
public class ConfigurationManager{
private static readonly Lazy<ConfigurationManager> _instance =
new Lazy<ConfigurationManager>(() => new ConfigurationManager());
private ConfigurationManager()
{
LoadConfiguration();
}
public static ConfigurationManager Instance => _instance.Value;
public string GetSetting(string key) => $"Value for {key}";
private void LoadConfiguration()
{
// Load config from file/database
}}
Characteristics:
β
One instance - Exactly one object in memory
β
Lazy initialization - Created when first needed
β
Can inherit - Can extend other classes
β
Can implement interfaces - Supports polymorphism
β
Testable - Can be mocked and tested
β Thread safety complexity - Requires careful implementation
β Hidden dependencies - Global state can be problematic
π Detailed Comparison
1. Memory & Performance
Static Class:
public static class Logger{
static Logger() // Static constructor called once
{
Console.WriteLine("Logger initialized at app start");
}
public static void Log(string message)
{
Console.WriteLine($"[LOG]: {message}");
}}
Memory: Loaded at application startup
Performance: Fastest access (no instance creation)
Lifetime: Lives for entire application lifetime
Singleton:
public class Logger{
private static readonly Lazy<Logger> _instance = new(() => new Logger());
private Logger()
{
Console.WriteLine("Logger instance created when first accessed");
}
public static Logger Instance => _instance.Value;
public void Log(string message)
{
Console.WriteLine($"[LOG]: {message}");
}}
Memory: Created on first access (lazy loading)
Performance: Slight overhead for instance creation
Lifetime: Lives until garbage collected (rare for singletons)
2. Inheritance & Polymorphism
Static Class - β No Inheritance:
// This won't compile!// public static class BaseLogger { }// public static class FileLogger : BaseLogger { } // Error!
public static class MathOperations{
public static int Add(int a, int b) => a + b;
// Cannot be overridden or inherited}
Singleton - β
Supports Inheritance:
public interface ILogger{
void Log(string message);}
public abstract class BaseLogger : ILogger{
public abstract void Log(string message);
protected void LogTimestamp()
{
Console.WriteLine($"Timestamp: {DateTime.Now}");
}}
public class FileLogger : BaseLogger{
private static readonly Lazy<FileLogger> _instance = new(() => new FileLogger());
public static FileLogger Instance => _instance.Value;
private FileLogger() { }
public override void Log(string message)
{
LogTimestamp();
// Write to file
Console.WriteLine($"File Log: {message}");
}}
// Polymorphism in actionILogger logger = FileLogger.Instance;logger.Log("This works!");
3. Testing & Mocking
Static Class - β Hard to Test:
public class OrderService{
public void ProcessOrder(Order order)
{
// Hard to mock EmailHelper.SendEmail()
EmailHelper.SendEmail(order.CustomerEmail, "Order processed");
// Tightly coupled to static class
DatabaseHelper.Save(order);
}}
// Testing becomes difficult - cannot mock static methods
Singleton - β
Easier to Test:
public interface IEmailService{
void SendEmail(string to, string message);}
public class EmailService : IEmailService{
private static readonly Lazy<EmailService> _instance = new(() => new EmailService());
public static EmailService Instance => _instance.Value;
private EmailService() { }
public void SendEmail(string to, string message)
{
// Send email implementation
}}
public class OrderService{
private readonly IEmailService _emailService;
public OrderService(IEmailService emailService = null)
{
_emailService = emailService ?? EmailService.Instance;
}
public void ProcessOrder(Order order)
{
_emailService.SendEmail(order.CustomerEmail, "Order processed");
}}
// Easy to test with mockvar mockEmail = new Mock<IEmailService>();var orderService = new OrderService(mockEmail.Object);
π― When to Use What?
Use Static Class When:
β
Utility functions (Math operations, string helpers)
β
No state management needed
β
Simple operations that don't require inheritance
β
Performance is critical (no instance overhead)
β
Extension methods
public static class StringExtensions{
public static bool IsValidEmail(this string email)
{
return email.Contains("@") && email.Contains(".");
}}
// Usagestring email = "[email protected]";bool isValid = email.IsValidEmail(); // Extension method
Use Singleton When:
β
Managing shared resources (Database connections, file handles)
β
Configuration management
β
Caching mechanisms
β
Need inheritance/interfaces
β
Lazy initialization is important
β
Testing/mocking is required
public class CacheManager : ICacheManager{
private static readonly Lazy<CacheManager> _instance = new(() => new CacheManager());
public static CacheManager Instance => _instance.Value;
private readonly Dictionary<string, object> _cache = new();
private CacheManager() { }
public T Get<T>(string key) where T : class
{
return _cache.ContainsKey(key) ? _cache[key] as T : null;
}
public void Set<T>(string key, T value) where T : class
{
_cache[key] = value;
}}
β οΈ Common Pitfalls & Best Practices
Static Class Pitfalls:
// β Bad - Static state can cause issuespublic static class UserManager{
private static string _currentUser; // Shared across all threads!
public static void SetCurrentUser(string user)
{
_currentUser = user; // Not thread-safe!
}}
// β
Good - Stateless static methodspublic static class PasswordHelper{
public static string HashPassword(string password)
{
// Pure function - no shared state
return BCrypt.Net.BCrypt.HashPassword(password);
}}
Singleton Pitfalls:
// β Bad - Not thread-safepublic class BadSingleton{
private static BadSingleton _instance;
public static BadSingleton Instance
{
get
{
if (_instance == null) // Race condition!
_instance = new BadSingleton();
return _instance;
}
}}
// β
Good - Thread-safe with Lazy<T>public class GoodSingleton{
private static readonly Lazy<GoodSingleton> _instance =
new Lazy<GoodSingleton>(() => new GoodSingleton());
public static GoodSingleton Instance => _instance.Value;
private GoodSingleton() { }}
π Summary Decision Matrix
| Requirement | Static Class | Singleton |
|---|
| No state needed | β
Perfect | β Overkill |
| Utility functions | β
Perfect | β Overkill |
| Need inheritance | β Impossible | β
Perfect |
| Need interfaces | β Impossible | β
Perfect |
| Lazy loading | β No | β
Yes |
| Unit testing | β Hard | β
Easy |
| Performance | β
Fastest | β οΈ Good |
| Memory usage | β
Minimal | β οΈ Small overhead |
| Thread safety | β
Built-in | β οΈ Manual |
π‘ Pro Tips:
Prefer static classes for utility functions and extension methods
Use Lazy for thread-safe singleton implementation
Consider dependency injection instead of singleton for better testability
Avoid singletons if you can use dependency injection container
Never mix static state with multithreading without proper synchronization