Поиск…


Статически инициализированный синглтон

public class Singleton
{
    private readonly static Singleton instance = new Singleton();
    private Singleton() { }
    public static Singleton Instance => instance;
}

Эта реализация является потокобезопасной, поскольку в этом случае instance объекта инициализируется в статическом конструкторе. CLR уже гарантирует, что все статические конструкторы выполняются поточно-безопасными.

Мутирующий instance не является потокобезопасной операцией, поэтому атрибут readonly гарантирует неизменность после инициализации.

Ленивый, потокобезопасный синглтон (с использованием Double Checked Locking)

Эта поточно-безопасная версия синглета была необходима в ранних версиях .NET, где static инициализация не гарантировалась для потоковой безопасности. В более современных версиях структуры статически инициализированный синглтон обычно предпочтительнее, потому что очень легко сделать ошибки реализации в следующем шаблоне.

public sealed class ThreadSafeSingleton
{
   private static volatile ThreadSafeSingleton instance;
   private static object lockObject = new Object();

   private ThreadSafeSingleton()
   {
   }

   public static ThreadSafeSingleton Instance
   {
      get 
      {
         if (instance == null) 
         {
            lock (lockObject) 
            {
               if (instance == null)
               {
                  instance = new ThreadSafeSingleton();
               }
            }
         }

         return instance;
      }
   }
}

Обратите внимание, что проверка if (instance == null) выполняется дважды: один раз до того, как блокировка будет получена, и один раз после этого. Эта реализация по-прежнему была бы потокобезопасной даже без первой проверки нуля. Однако это будет означать, что блокировка будет получена каждый раз, когда запрашивается экземпляр, и это может привести к потере производительности. Первая нулевая проверка добавляется, чтобы блокировка не была получена, если это не необходимо. Вторая нулевая проверка гарантирует, что только первый поток, получающий блокировку, затем создает экземпляр. Другие потоки найдут экземпляр, который будет заполнен, и пропустите вперед.

Ленивый, потокобезопасный Синглтон (с использованием Lazy )

.Net 4.0 Lazy гарантирует инициализацию объектов с потоком, поэтому этот тип можно использовать для создания Singletons.

public class LazySingleton
{
    private static readonly Lazy<LazySingleton> _instance =
        new Lazy<LazySingleton>(() => new LazySingleton());
 
    public static LazySingleton Instance
    {
        get { return _instance.Value; }
    }

    private LazySingleton() { }
}

Использование Lazy<T> гарантирует, что объект создается только тогда, когда он используется где-то в вызывающем коде.

Простое использование будет выглядеть так:

using System;
                    
public class Program
{
    public static void Main()
    {
        var instance = LazySingleton.Instance;
    }
}

Живая демонстрация на .NET скрипке

Lazy, потокобезопасный singleton (для .NET 3.5 или старше, альтернативная реализация)

Поскольку в .NET 3.5 и старше у вас нет класса Lazy<T> вы используете следующий шаблон:

public class Singleton
{
    private Singleton() // prevents public instantiation
    {
    }

    public static Singleton Instance
    {
        get
        {
            return Nested.instance;
        }
    }
    
    private class Nested
    {
        // Explicit static constructor to tell C# compiler
        // not to mark type as beforefieldinit
        static Nested()
        {
        }

        internal static readonly Singleton instance = new Singleton();
    }
}

Это вдохновляет блог Джона Скита .

Поскольку класс Nested является вложенным и закрытым, экземпляр экземпляра singleton не запускается при доступе к другим членам класса Sigleton (например, к Sigleton readonly).

Устранение экземпляра Singleton, когда он больше не нужен

В большинстве примеров создается экземпляр и LazySingleton объект LazySingleton до тех пор, пока приложение-владелец не завершится, даже если этот объект больше не нужен приложению. Решением этого является реализация IDisposable и установка экземпляра объекта в null следующим образом:

public class LazySingleton : IDisposable
{
    private static volatile Lazy<LazySingleton> _instance;
    private static volatile int _instanceCount = 0;
    private bool _alreadyDisposed = false;

public static LazySingleton Instance
{
    get
    {
        if (_instance == null)
            _instance = new Lazy<LazySingleton>(() => new LazySingleton());
        _instanceCount++;
        return _instance.Value;
    }
}

private LazySingleton() { }

// Public implementation of Dispose pattern callable by consumers.
public void Dispose()
{ 
    if (--_instanceCount == 0) // No more references to this object.
    {       
       Dispose(true);
       GC.SuppressFinalize(this);           
    }
}

// Protected implementation of Dispose pattern.
protected virtual void Dispose(bool disposing)
{
    if (_alreadyDisposed) return; 
  
    if (disposing) 
    {
        _instance = null; // Allow GC to dispose of this instance.
        // Free any other managed objects here.
    }
  
    // Free any unmanaged objects here.
    _alreadyDisposed = true;
}

Вышеупомянутый код предоставляет экземпляр до завершения приложения, но только если потребители вызывают Dispose() на объекте после каждого использования. Поскольку нет никакой гарантии, что это произойдет или способ заставить его, также нет гарантии, что экземпляр когда-либо будет удален. Но если этот класс используется внутри, то легче обеспечить, чтобы метод Dispose() вызывался после каждого использования. Ниже приведен пример:

public class Program
{
    public static void Main()
    {
        using (var instance = LazySingleton.Instance)
        {
            // Do work with instance
        }
    }
}

Обратите внимание, что этот пример не является потокобезопасным .



Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow