Understanding the Singleton Design Pattern in C#

The Singleton design pattern is one of the most widely used creational design patterns. It ensures that a class has only one instance while providing a global point of access to it. This is particularly useful when exactly one object is needed to coordinate actions across the system.



In this blog post, we will explore the Singleton pattern in C#, understand why it’s useful, and review various implementations including thread safety and the usage of the Lazy<T> class. We will also look at how each of these approaches works with example code.

Why Use the Singleton Pattern?

 
The primary purpose of the Singleton pattern is to control object creation by limiting the number of instances to just one. Some of the common use cases include:

  1. Configuration management: A centralized configuration object that needs to be accessed across different parts of the application.
  2. Logging: Logging systems should have a single instance for maintaining a consistent log throughout the application.
  3. Caching: A single cache instance is often used to store data in memory that can be shared throughout the application. 

Basic Implementation: Singleton Without Thread-Safety:

 
Let's start with a simple implementation of the Singleton pattern without handling thread safety.


 namespace singleton_example  
 {  
   public sealed class singletonSample  
   {  
     private static singletonSample singleton;  
     private static int i = 1;  
   
     private singletonSample()  
     {  
       Console.WriteLine($"Object initialized {i++} times");  
     }   
   
     public static singletonSample CreateInstance()  
     {  
       if (singleton == null)  
       {  
         singleton = new singletonSample();  
       }  
   
       return singleton;  
     }  
   
     public void Print(string input)  
     {  
       Console.WriteLine(input);  
     }  
   }  
 }  
   

Code Explanation

 

  1. Sealed class: The singletonSample class is marked as sealed to prevent any other class from inheriting it and breaking the singleton behavior.
  2. Private constructor: The constructor is marked as private so that the class cannot be instantiated from outside the class.
  3. Static field: singleton is a static field that holds the single instance of the class. It is checked inside the CreateInstance method.
  4. CreateInstance method: This method checks if the instance singleton is null. If so, it creates a new instance of the class, otherwise, it returns the existing instance.
  5. Print method: This is just a utility method to demonstrate the functionality of the singleton class.

Note: This implementation is not thread-safe. If multiple threads try to access CreateInstance at the same time, they might end up creating multiple instances.

Thread-Safe Singleton Implementation:

 
In a multi threaded environment, the above implementation could cause issues because two or more threads could simultaneously check for null and create multiple instances of the Singleton class. To fix this, we need to introduce thread synchronization.


 namespace singleton_example  
 {  
   public sealed class singletonSample  
   {  
     private static singletonSample singleton;  
     private static int i = 1;  
     private static readonly object obj = new object();  
   
     private singletonSample()  
     {  
       Console.WriteLine($"Object initialized {i++} times");  
     }  
   
     public static singletonSample CreateInstance()  
     {  
       if (singleton == null)  
       {  
         lock (obj)  
         {  
           if (singleton == null)  
           {  
             singleton = new singletonSample();  
           }  
         }  
       }  
       return singleton;  
     }  
   
     public void Print(string input)  
     {  
       Console.WriteLine(input);  
     }  
   }  
 }  
   

Code Explanation

  1. Thread synchronization: The lock keyword ensures that only one thread can enter the critical section at a time. This prevents multiple threads from creating multiple instances of the singleton class.
  2. Double-check locking: Even though we are using a lock to synchronize the code, we still need to check if singleton is null inside the lock block. This is because the first check happens outside the lock and could potentially be accessed by multiple threads.

This implementation ensures that only one instance of the singleton class is created, even in a multi threaded environment. However, this can still be improved further with simpler and more efficient implementations.

Singleton with Lazy<T> (Thread-Safe and Lazy Initialization)
The Lazy<T> class in C# provides an easy way to implement the singleton pattern with thread safety and lazy initialization built-in. This removes the need for explicit locking and double-checking.


 namespace singleton_example  
 {  
   public sealed class singletonSample  
   {  
     private static int i = 1;  
   
     private singletonSample()  
     {  
       Console.WriteLine($"Object initialized {i++} times");  
     }  
   
     private static readonly Lazy<singletonSample> lazySingleton = new Lazy<singletonSample>(() => new singletonSample());  
   
     public static singletonSample CreateInstance()  
     {  
       return lazySingleton.Value;  
     }  
   
     public void Print(string input)  
     {  
       Console.WriteLine(input);  
     }  
   }  
 }  
   

Code Explanation

  1. Lazy<T>: The Lazy<T> type ensures that the instance is only created when it’s accessed for the first time, i.e., lazy initialization. This also inherently handles thread safety.
  2. Simplified implementation: We no longer need to manually implement locking mechanisms or check if the instance is null. The Lazy<T> class does this for us.


This is the most elegant and recommended approach for implementing the Singleton pattern in C#, especially when working in a multi threaded environment.

Conclusion:
The Singleton pattern is a simple yet powerful design pattern that ensures a class has only one instance. As seen in the above implementations:

  1. A basic singleton without thread safety is easy to implement but unsuitable for multi threading.
  2. Thread safety can be introduced using lock and double-checked locking to prevent multiple instances in multi threaded applications.
  3. The Lazy<T> class provides a clean and thread-safe way to implement the Singleton pattern with minimal code.
  4. The choice of implementation depends on your application's needs. If thread safety is not a concern, the basic implementation works fine. However, in most real-world applications, thread safety is crucial, and using Lazy<T> is often the best choice.

Comments

Popular posts from this blog

Implement Logging in CSV file using Nlog in .net core MVC application- part 2

Implement Nlog in .Net core MVC application part 1

Angular User Session Timeout example step by step

Restore the lost focus of Auto post back controls in asp.net update Panel control

Disable backspace key using Jquery