Resolve the singleton pattern of C#design pattern

Singleton, so the idea is that there should only be one instance of an object throughout the application. For example, a class loads data from a database into memory to provide read-only data, which makes it ideal to use the singleton mode because it is not necessary to load multiple copies of the same data in memory. In addition, in some cases, it is not allowed to have multiple copies of the same data in memory, such as data that is too large to hold two copies of the same data in memory, and so on.

Contract Singleton by Convention

It's a bit " Too simple, Sometimes naïve",He just prompts the user that I'm a singleton and don't reinitialize me, for example:
public class Database
{
  /// <summary>
  ///Warning, this is a singleton, do not initialize more than once, otherwise, at your own risk.
  /// </summary>
  public Database() {}
};

One is that you won't notice this prompt at all, and secondly, many times, these initializations occur by stealth or unintentional means, such as through reflection, factory generation (Activator.CreateInstance), injection, and so on, although there is a "convention greater than configuration", they are not used here.

The most common idea for singleton mode is to provide a global, static object.

public static class Globals
{
  public static Database Database = new Database();
}

This is not a safe way. This does not prevent users from new databases elsewhere, and users may not know that there is a Globals class with a Database singleton in it.

Classic implementation

The only way to prevent users from instantiating objects is to make the constructor private and provide methods or properties that return unique internal objects.

public class Database
{
  private Database() { ... }
  public static Database Instance { get; } = new Database();
}

Now set the constructor to private, and of course private can continue to be called through reflection, but this requires additional action, which prevents most users from instantiating it directly. By defining an instance as static, you extend its life cycle until the application is running.

Delayed Initialization

The above methods are thread safe, but because they are static properties, they are initialized before all instances of the class are created, or before any static member is accessed, and only once in each AppDomain.

How to implement delayed initialization, that is, to defer the construction of a single object until the first time the application requests it, if the application never requests the object, the object will never be constructed. Previously, you could use the double check method. In fact, there are still some problems to be noticed in order to achieve the correct double check. For example, in the example above, the first version might be written like this, using a lock.

public class Database
{
  private static Database db;
  private static object olock = new object();
  private Database()
  { }
 
  public static Database GetInstance()
  {
    lock (olock)
    {
      if (db == null)
      {
        db = new Database();
      }
      return db;
    }
  }
}

This is thread safe, but every time you access GetInstance, whether the object has been created or not, you need to acquire and release locks, which is resource intensive, so add another layer of judgment outside.

public class Database
{
  private static Database db;
  private static object olock = new object();
  private Database()
  { }
 
  public static Database GetInstance()
  {
    if (db == null)
    {
      lock (olock)
      {
        if (db == null)
        {
          db = new Database();
        }
      }
    }
    return db;
  }
}

Determine whether the object has been initialized before accessing it, and if the initialization returns directly, one access to the lock is avoided. But, there's still a problem here. Assuming that Database is time consuming to initialize, Thread B determines at the outermost level whether DB is empty when Thread A acquires a lock while initializing db. Thread A initializes db, possibly only partially. At this time DB may not be empty, returning objects that are not fully initialized, which may cause Thread B to crash.

The solution is to store the object in a temporary variable and then in db as an atomic write, as follows

public class Database
{
  private static Database db;
  private static object olock = new object();
  private Database()
  { }
 
  public static Database GetInstance()
  {
    if (db == null)
    {
      lock (olock)
      {
        if (db == null)
        {
          var temp = new Database();
          Volatile.Write(ref db, temp);
        }
      }
    }
    return db;
  }
}

Very cumbersome, although delayed initialization is implemented, it is too complex to compare with the static field at the beginning and can be written incorrectly if you do not care. Although it can be simplified to:

public static Database GetInstance()
{
  if (db == null)
  {
    var temp = new Database();
    Interlocked.CompareExchange(ref db, temp, null);
  }
  return db;
}

This method appears to be unlocked, and temp objects may be initialized twice, but when temp is written to db, Interlock.CompareExchange ensures that only one object is correctly written to DB and that temp objects that are not written are garbage collected faster than the double check above. But there is still a cost of learning. Fortunately, C#provides the Lazy method:

private static Lazy<Database> db = new Lazy<Database>(() => new Database(), true);
public static Database GetInstance() => db.Value;

Simple and perfect.

Dependent Injection and Singleton Mode

The previous singleton pattern is actually a code-intrusive practice, that is, to implement a singleton with code that did not implement the singleton, you need to modify the code implementation, and the code is prone to error. Some people think that the only correct approach for the singleton pattern is in IOC dependent injection, which does not require modifying the source code, implements dependent injection by relying on the injection framework, in a unified entry, in a unified management life cycle, in ASP.NET Core MVC, in ConfigureServices code for Startup:

services.AddSingleton<Database>();

Or join the places where IDatabase is needed, using the Database singleton:

services.AddSingleton<IDatabase,Database>();

At ASP. In the subsequent code of NET Core MVC, where IDatabase is used, it will be implemented by a single instance of Database, without any modification within the Database. When using, you only need to refer to the GetService method in the IServiceProvider interface, which is made by ASP. The IOC framework of NET Core MVC is directly available and does not require special handling:

public XXXController(IServiceProvider serviceProvider)
{
   var db = serviceProvider.GetService<IDatabase>();
}

Monostate

A singleton pattern is a variant of the singleton pattern, which is a common class, but behaves and behaves like the singleton pattern.

For example, if we are modeling a company's personnel structure, a typical company will typically only have one CEO,
public class ChiefExecutiveOfficer
{
  private static string name;
  private static int age;
 
  public string Name
  {
    get => name;
    set => name = value;
  }
 
  public int Age
  {
    get => age;
    set => age = value;
  }
}

The properties of this class have get, set, but the private fields behind it c#Tutorial They are all static. So no matter how many times this ChiefExecutiveOfficer is instantiated, its internal references are the same data. For example, two objects can be instantiated, but their contents are identical.

Singleton modes are simple but confusing, so to be simple, to achieve a singleton effect

Keywords: C#

Added by FamousMortimer on Thu, 03 Mar 2022 21:55:29 +0200