SoFunction
Updated on 2025-04-08

C# Design Pattern Series Tutorial-Singleton Mode

1. Description:

Ensure that a class has only one instance and provides a global access point to access it.

2. The singleton model has three main characteristics:

2.1 The singleton class ensures that it has only one instance.
2.2 The singleton class must create its own instance.
2.3 The singleton class must provide a unique instance for other objects.

3. Implementation method: lazy singleton class and hungry singleton class

3.1 Lazy singleton class
For the lazy mode, we can understand it like this: this singleton class is very lazy and will only act when it needs it, and never knows to prepare early. It only determines whether there is an object when it needs an object. If it does not, create an object immediately and then return it. If there is an object, it will no longer be created and return immediately.
Lazy mode is only created when the external object first requests an instance.

3.2 Single case of hungry man
For the hungry man pattern, we can understand it like this: the singleton class is very hungry and needs to eat, so it creates an object immediately when the class is loaded.

3.3 Pros and cons of lazy mode and hungry mode:
Lazy mode, its characteristic is that it is slow to obtain objects at runtime, but it is faster to load classes. It only takes up resources in part of the life cycle of the application.
The feature of Hungry Man mode is that it is slow to load classes, but it is faster to obtain objects during runtime. It will consume resources from loading until the end of the application.
These two modes do not have much performance differences for lightweight objects that are initialized faster and occupy less resources. There is no problem in choosing lazy or hungry. However, for heavyweight objects with slow initialization and occupancy of resources, there will be a relatively obvious difference. Therefore, if you apply Hungry mode to heavyweight objects, the class loading speed is slow, but the running speed is fast; the lazy mode is the opposite, the class loading speed is fast, but the first time you get the object is slow.
From the perspective of user experience, we should first choose the Hungry Man mode. We are willing to wait for a program to take a long time to initialize, but we don’t like waiting too long when the program is running, which gives people a feeling of being slow to react. Therefore, for singleton modes with heavyweight objects participating, we recommend using the Hungry Man mode.
For lightweight objects with faster initialization, you can choose any method. If a large number of singleton patterns are used in an application, we should weigh two methods. The singleton of lightweight objects adopts the lazy mode to reduce the loading burden, shorten the loading time, and improve loading efficiency. At the same time, since they are lightweight objects, the creation of these objects is actually to allocate the time spent creating a singleton object to the entire application, which does not have much impact on the operation efficiency of the entire application.

4. Code implementation:

4.1 Lazy

  public class Singleton
  {
    private static Singleton m_Instance;

    private Singleton()
    {
      // Define the default constructor as private to prevent external calls from instantiating other objects    }

    public static Singleton GetInstance()
    {

      if (m_Instance == null)
      {
        m_Instance = new Singleton();
      }

      return m_Instance;
    }
  }

4.2 Hungry Man Style

  // Define as sealed to prevent derivation, because derivation may add instances  public sealed class Singleton
  {
    private static readonly Singleton m_Instance = new Singleton();
    private Singleton()
    {
      // Define the default constructor as private to prevent external calls from instantiating other objects    }

    public static Singleton GetInstance()
    {
      return m_Instance;
    }
  }

5. Model summary

5.1 Advantages:
Prevents instantiation of multiple objects in the application. This saves overhead, each instance takes up a certain amount of memory, and it takes time and space to create objects.

5.2 Disadvantages:

5.3 Applicable occasions:
5.3.1 Control the use of resources and control the concurrent access of resources through thread synchronization;
5.3.2 Control the number of instances generated to achieve the purpose of saving resources.
5.3.3 Used as a communication medium, that is, data sharing, it can enable communication between two unrelated threads or processes without establishing direct association.

5.4 Support for design principles:

The most core point of using singleton mode is that it embodies the "single responsibility" principle in the object-oriented encapsulation feature.

6. Supplement: During the multi-threading process, it is possible to use the lazy singleton pattern to prevent two threads from instantiating the object at the same time.

The solution is given below

6.1 Using the lock mechanism

  public class Singleton
  {
    private static Singleton m_Instance;

    static readonly object o = new object();

    private Singleton()
    {
      // Define the default constructor as private to prevent external calls from instantiating other objects    }

    public static Singleton GetInstance()
    {
      lock (o)
      {
        if (m_Instance == null)
        {
          m_Instance = new Singleton();
        }
      }

      return m_Instance;
    }
  }

Using the lock mechanism can prevent two threads from creating objects at the same time, but there is a performance problem here. Whenever a thread accesses GetInstance(), it is necessary to add a lock, which is actually unnecessary.

6.2 Double lock

  public class Singleton
  {
    private static Singleton m_Instance;

    static readonly object o = new object();

    private Singleton()
    {
      // Define the default constructor as private to prevent external calls from instantiating other objects    }

    public static Singleton GetInstance()
    {
      // Here is a added to determine whether the instance exists. Lock is only added when it does not exist, that is, only locks are added once in the life cycle of this instance      if (m_Instance == null)
      {
        lock (o)
        {
          if (m_Instance == null)
          {
            m_Instance = new Singleton();
          }
        }
      }

      return m_Instance;
    }
  }

Double locking ensures that the instance is locked only once during its life cycle, so it has no impact on performance.

The above is the entire content of this article. I hope you can give you a reference and I hope you can support me more.