Static vs Singleton in C#: Understanding the Key Differences

In this article, we will explore the Static vs Singleton in C# with code examples and detailed explanations.

static-vs-singleton

What is a Static Class?

In C#, a static class is a class that cannot be instantiated. It is designed to hold static members, such as methods and properties, that can be accessed without creating an instance of the class. Static classes are defined using the static keyword.

Example of a Static Class:

using System;

public static class MathUtility
{
    public static int Add(int a, int b)
    {
        return a + b;
    }
}
class Program
{
    static void Main()
    {

        int result = MathUtility.Add(5, 10);
        Console.Write($"Output: {result}");
        Console.ReadLine();
    }
}
// Output: 15

In this example, the MathUtility class contains a static method Add that can be called directly without creating an instance of the class.

What is a Singleton?

A singleton is a design pattern that ensures a class has only one instance and provides a global point of access to that instance. This pattern is useful when you want to restrict the instantiation of a class to one object and provide a way to access that object from any part of your code.

Example of a Singleton Class:

public class Logger
{
    private static Logger _instance;
    
    private Logger() { }
    
    public static Logger Instance
    {
        get
        {
            if (_instance == null)
            {
                _instance = new Logger();
            }
            return _instance;
        }
    }
    
    public void Log(string message)
    {
        // Log the message
    }
}

In this example, the Logger class is a singleton, and the only way to access its instance is through the Instance property.

Difference between Singleton and Static Class in C#

Some important points about Static vs Singleton in C# are:

  • Static is a keyword that can be applied to classes, methods, fields, properties, etc. Singleton is a design pattern that ensures only one instance of a class exists in the application.
  • Static classes can only have static members and methods and cannot be instantiated. Singleton classes can have static and non-static members and methods and can be instantiated once.
  • Static classes cannot implement interfaces or inherit from other classes. Singleton classes can implement interfaces and inherit from other classes.
  • Static classes are initialized when the CLR first loads them. Singleton classes are initialized when the first call to the instance is made.
  • Static classes cannot be serialized or passed as arguments to methods. Singleton classes can be serialized and passed as arguments to methods.
  • Static classes are stored in a special area of the heap known as the High-Frequency Heap that is not garbage collected, while Singleton classes are stored in the normal heap.
  •  Static classes cannot implement IDisposable or have parameters in their constructors, while Singleton classes can. 
  • Static classes offer faster method calls as they are bound at compile time, while Singleton classes can be lazy-loaded when needed.
  • Performing the unit testing with static classes can be challenging because they cannot be instantiated or easily mocked. Singleton classes, on the other hand, are more unit-test-friendly. You can create mock implementations of a singleton class and inject them into the code being tested.

Common Use Cases of Static Class and Singleton

When to Use Static Class:

Static classes are suitable for scenarios where you need utility functions or constants that don’t require state. Common use cases for static classes include:

When to Use Singleton

Singletons are appropriate when you need a single point of control or coordination in your application. They ensure that a single instance of a class is responsible for managing a specific resource or functionality. 

Here are some common and detailed use cases for singleton classes:

01. Logging Mechanisms

Use Case: Singleton classes are ideal for implementing logging mechanisms in your application. Logging is crucial for debugging, auditing, and monitoring your software’s behavior. Using a Singleton Logger ensures that all log entries are consolidated and consistent throughout your application.

02. Database Connections

Use Case: You must agree that managing database connections efficiently is essential for any data-driven application. Singleton classes can create and manage a single, shared database connection instance. This ensures that database resources are used optimally and effectively utilize connection pooling.

Benefits:

  • It reduces the overhead of opening and closing database connections repeatedly.
  • It prevents resource exhaustion by limiting the number of concurrent database connections.
  • Singleton object simplifies database access throughout the application.

03. Configuration Managers

Use Case: Configuration managers are responsible for handling application settings, such as connection strings, API keys, and various parameters. A Singleton Configuration Manager can provide a centralized location for storing and retrieving these settings, ensuring consistency and ease of maintenance.

Benefits:

  • Centralized configuration management simplifies updates and maintenance.
  • It provides a single point of access to application settings.
  • It supports dynamic reloading of configuration settings without restarting the application.

04. Caching Mechanisms

Use Case: Caching is a technique that stores frequently accessed data in memory to improve application performance. A Singleton Cache Manager can manage the caching strategy and ensure that cached data is consistent and efficiently utilized across the application.

Benefits:

  • Enhances application responsiveness by reducing database or external service calls.
  • It provides a consistent caching strategy throughout the application.
  • It Supports cache expiration, eviction policies, and data consistency.

Example 1: Simple Singleton

public class SimpleSingleton
{
    // Private static instance of the Singleton
    private static SimpleSingleton _instance;

    // Private constructor to prevent direct instantiation
    private SimpleSingleton()
    {
        // Initialization code (if needed)
    }

    // Public property to access the Singleton instance
    public static SimpleSingleton Instance
    {
        get
        {
            // Lazy initialization: Create the instance if it doesn't exist
            if (_instance == null)
            {
                _instance = new SimpleSingleton();
            }
            return _instance;
        }
    }

    // Public method of the Singleton
    public void DoSomething()
    {
        Console.WriteLine("Singleton is doing something.");
    }
}

Code Explanation: In this example, we have created a simple Singleton class named SimpleSingleton. It follows the classic Singleton pattern, where the instance is lazily initialized when the Instance property is first accessed. The private constructor ensures that the class cannot be instantiated directly. The DoSomething method demonstrates how you can perform actions using the Singleton instance.

Example 2: Thread-Safe Singleton with Double-Check Locking

public class ThreadSafeSingleton
{
    // Private static instance of the Singleton
    private static ThreadSafeSingleton _instance;

    // Private object for locking
    private static readonly object _lock = new object();

    // Private constructor to prevent direct instantiation
    private ThreadSafeSingleton()
    {
        // Initialization code (if needed)
    }

    // Public property to access the Singleton instance
    public static ThreadSafeSingleton Instance
    {
        get
        {
            // Double-check locking to ensure thread safety
            if (_instance == null)
            {
                lock (_lock)
                {
                    if (_instance == null)
                    {
                        _instance = new ThreadSafeSingleton();
                    }
                }
            }
            return _instance;
        }
    }

    // Public method of the Singleton
    public void DoSomething()
    {
        Console.WriteLine("Thread-safe Singleton is doing something.");
    }
}

Code Explanation: This example demonstrates a thread-safe Singleton using double-check locking. The Instance property ensures that only one instance is created, even in a multi-threaded environment. The lock statement prevents multiple threads from entering the initialization block simultaneously, avoiding race conditions.

Example 3: Singleton with Lazy Initialization

public class LazySingleton
{
    // Private static instance of the Singleton
    private static readonly Lazy<LazySingleton> _lazyInstance =
        new Lazy<LazySingleton>(() => new LazySingleton());

    // Private constructor to prevent direct instantiation
    private LazySingleton()
    {
        // Initialization code (if needed)
    }

    // Public property to access the Singleton instance
    public static LazySingleton Instance => _lazyInstance.Value;

    // Public method of the Singleton
    public void DoSomething()
    {
        Console.WriteLine("Lazy Singleton is doing something.");
    }
}

Code Explanation: This example uses C#’s Lazy<T> type to achieve lazy initialization. The Instance property leverages the lazy initialization provided by _lazyInstance. The Singleton instance is created only when Instance is accessed for the first time, ensuring efficient resource usage.

Example 2: Static Class Example

Here’s a code example of a static class named MathUtility with static methods.

using System;

/// <summary>
/// A static class that provides mathematical utility functions.
/// </summary>
public static class MathUtility
{
   
    // Adds two integers and returns the result.  
    public static int Add(int a, int b)
    {
        return a + b;
    }

    
    // Subtracts one integer from another and returns the result.
   
    public static int Subtract(int a, int b)
    {
        return a - b;
    }

   
    // Multiplies two integers and returns the result.
    
    public static int Multiply(int a, int b)
    {
        return a * b;
    }

    
    // Divides one integer by another and returns the result.
   
    public static double Divide(int a, int b)
    {
        if (b == 0)
        {
            throw new DivideByZeroException("Cannot divide by zero.");
        }
        return (double)a / b;
    }
}

class Program
{
    static void Main()
    {
        int resultAdd = MathUtility.Add(5, 3);
        Console.WriteLine("5 + 3 = " + resultAdd);

        int resultSubtract = MathUtility.Subtract(10, 4);
        Console.WriteLine("10 - 4 = " + resultSubtract);

        int resultMultiply = MathUtility.Multiply(6, 7);
        Console.WriteLine("6 * 7 = " + resultMultiply);

        try
        {
            double resultDivide = MathUtility.Divide(8, 2);
            Console.WriteLine("8 / 2 = " + resultDivide);
        }
        catch (DivideByZeroException ex)
        {
            Console.WriteLine("Error: " + ex.Message);
        }
        Console.ReadKey();
    }
}

Output:

5 + 3 = 8
10 - 4 = 6
6 * 7 = 42
8 / 2 = 4

Code Explanation: In this code example, the MathUtility static class provides four mathematical utility functions: Add, Subtract, Multiply, and Divide. Each method is with a brief explanation of its purpose, parameters, and return value. 

The Main method in the Program class demonstrates how to use these utility methods to perform basic mathematical operations.

FAQs

Q1: What is the main difference between a Singleton and a Static class in C#?

A Singleton class is designed to have only one instance and provides a way to access that instance globally. On the other hand, a Static class cannot be instantiated and primarily contains static members like methods and properties that can be accessed directly without creating an instance using the new keyword.

Q2: When should I use a Singleton class in my C# application?

Singleton classes are appropriate when you need a single point of control or coordination in your application. Use them for managing resources like database connections, logging mechanisms, caching, and configuration managers. Singleton ensures that only one instance is responsible for these resources.

Q3: Are Singleton classes thread-safe by default?

No, Singleton classes are not thread-safe by default. Developers need to implement thread safety mechanisms if the application is multi-threaded. Common techniques include using locks, double-check locking, or leveraging the Lazy<T> class for lazy initialization in a thread-safe manner.

Q4: Can I inherit from a Singleton class in C#?

Yes, you can inherit from a Singleton class. Singleton classes support inheritance and allow you to create derived classes that extend their functionality. However, Static classes are sealed and cannot be inherited.

Q5: What is the advantage of using a Static class in C#?

Static classes have better performance compared to Singleton classes in certain scenarios. This is because static methods are bound at compile time, resulting in faster method calls. Use Static classes for utility functions( Example: Math and Console class) and constants that don’t require state.

Articles you might also like:

Shekh Ali
4.5 2 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Inline Feedbacks
View all comments