Синглтон шаблон для C#

Мне нужно хранить кучу переменных, к которым нужно обращаться глобально, и мне интересно, будет ли применим шаблон синглтона. Из примеров, которые я видел, шаблон синглтона - это просто статический класс, который не может быть унаследован. Но примеры, которые я видел, слишком сложны для моих нужд. Какой будет самый простой класс-синглтон? Не мог бы я просто сделать статический, запечатанный класс с некоторыми переменными внутри?

8 ответов

Решение

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

Я не знаю, какие примеры вы видели, но обычно в C# шаблон синглтона может быть очень простым:

public sealed class Singleton
{
    private static readonly Singleton instance = new Singleton();
    static Singleton() {} // Make sure it's truly lazy
    private Singleton() {} // Prevent instantiation outside

    public static Singleton Instance { get { return instance; } }
}

Это не сложно.

Преимущество одиночного элемента по сравнению со статическими членами заключается в том, что класс может реализовывать интерфейсы и т. Д. Иногда это полезно, но в других случаях статические члены действительно будут работать так же хорошо. Кроме того, обычно легче перейти от одноэлементного к не одноэлементному позже, например, передать одиночный объект как объект "конфигурации" классам зависимостей, а не тем классам зависимостей, которые выполняют прямые статические вызовы.

Лично я бы старался по возможности избегать использования синглетонов - они затрудняют тестирование, помимо всего прочего. Хотя иногда они могут быть полезны.

Есть несколько паттернов, которые могут вам подойти, синглтон - один из худших.

реестр

struct Data {
  public String ProgramName;
  public String Parameters;
}

class FooRegistry {
  private static Dictionary<String, Data> registry = new Dictionary<String, Data>();
  public static void Register(String key, Data data) {
     FooRegistry.registry[key] = data;
  }
  public static void Get(String key) {
     // Omitted: Check if key exists
     return FooRegistry.registry[key];
  }
}

преимущества

  • Легко переключиться на Mock Object для автоматического тестирования
  • Вы все еще можете хранить несколько экземпляров, но при необходимости у вас есть только один экземпляр.

Недостатки

  • Немного медленнее, чем синглтон или глобальная переменная

Статический класс

class GlobalStuff {
  public static String ProgramName {get;set;}
  public static String Parameters {get;set;}
  private GlobalStuff() {}
}

преимущества

  • просто
  • Быстро

Недостатки

  • Трудно динамически переключаться на то есть Mock Object
  • Трудно переключиться на другой тип объекта, если требования изменяются

Простой синглтон

class DataSingleton {
  private static DataSingleton instance = null;
  private DataSingleton() {}
  public static DataSingleton Instance {
     get {
         if (DataSingleton.instance == null) DataSingleton.instance = new DataSingleton();
         return DataSingleton;
     }
  }
}

преимущества

  • Ни один на самом деле

Недостатки

  • Трудно создать потокобезопасный синглтон, вышеприведенная версия не будет работать, если несколько экземпляров получат доступ к экземпляру
  • Трудно переключиться на фиктивный объект

Лично мне нравится шаблон реестра, но YMMV.

Вы должны взглянуть на Внедрение Зависимостей, так как это обычно считается лучшей практикой, но это слишком большая тема, чтобы объяснять здесь:

Внедрение зависимости

Singleton - это не просто статический класс, который не может быть унаследован. Это обычный класс, который может быть создан только один раз, когда каждый разделяет этот единственный экземпляр (и сделать его потокобезопасным - еще больше работы).

Типичный код.NET для Singleton выглядит примерно так: Это быстрый пример, и ни в коем случае не лучшая реализация или потокобезопасный код:

public sealed class Singleton
{
    Singleton _instance = null;

    public Singleton Instance
    {
        get
        {
            if(_instance == null)
                _instance = new Singleton();

            return _instance;
        }
    }

    // Default private constructor so only we can instanctiate
    private Singleton() { }

    // Default private static constructor
    private static Singleton() { }
}

Если вы собираетесь идти по пути, который вы думаете, статический запечатанный класс будет работать просто отлично.

Использование инициализаторов авто-свойств C# 6.

public sealed class Singleton
{
    private Singleton() { }
    public static Singleton Instance { get; } = new Singleton();
}

Коротко и чисто - буду рад услышать минусы.

Я знаю, что эта проблема старая, но здесь есть другое решение, использующее.Net 4.0 или более позднюю версию (включая.Net Core и.Net Standard).

Сначала определите ваш класс, который будет преобразован в синглтон:

public class ClassThatWillBeASingleton
{
    private ClassThatWillBeASingleton()
    {
        Thread.Sleep(20);
        guid = Guid.NewGuid();
        Thread.Sleep(20);
    }

    public Guid guid { get; set; }
}

В этом примере класса я определил один конструктор, который некоторое время спит, а затем создает один новый Guid и сохраняет его в своем публичном свойстве. (Сон только для тестирования параллелизма)

Обратите внимание, что конструктор является закрытым, так что никто не может создать новый экземпляр этого класса.

Теперь нам нужно определить оболочку, которая преобразует этот класс в синглтон:

public abstract class SingletonBase<T> where T : class
{
    private static readonly Lazy<T> _Lazy = new Lazy<T>(() =>
    {
        // Get non-public constructors for T.
        var ctors = typeof(T).GetConstructors(System.Reflection.BindingFlags.Instance |
                                              System.Reflection.BindingFlags.NonPublic);
        if (!Array.Exists(ctors, (ci) => ci.GetParameters().Length == 0))
            throw new InvalidOperationException("Non-public ctor() was not found.");
        var ctor = Array.Find(ctors, (ci) => ci.GetParameters().Length == 0);
        // Invoke constructor and return resulting object.
        return ctor.Invoke(new object[] { }) as T;
    }, System.Threading.LazyThreadSafetyMode.ExecutionAndPublication);

    public static T Instance
    {
        get { return _Lazy.Value; }
    }
}

Обратите внимание, что он использует Lazy для создания поля _Lazy он знает, как создать экземпляр класса, используя его приватный конструктор.

И это определяет одно свойство Instance чтобы получить доступ к полю "Ленивый".

Обратите внимание на LazyThreadSafetyMode enum, который передается конструктору Lazy. Он использует ExecutionAndPublication, Таким образом, только одному потоку будет разрешено инициализировать значение поля Lazy.

Теперь все, что нам нужно сделать, это определить упакованный класс, который будет одиночным:

public class ExampleSingleton : SingletonBase<ClassThatWillBeASingleton>
{
    private ExampleSingleton () { }
}

Вот один из примеров использования:

ExampleSingleton.Instance.guid;

И один тест, чтобы утверждать, что два потока получат один и тот же экземпляр Singleton:

[Fact()]
public void Instance_ParallelGuid_ExpectedReturnSameGuid()
{
    Guid firstGuid = Guid.Empty;
    Guid secondGuid = Guid.NewGuid();

    Parallel.Invoke(() =>
    {
        firstGuid = Singleton4Tests.Instance.guid;
    }, () =>
    {
        secondGuid = Singleton4Tests.Instance.guid;
    });

    Assert.Equal(firstGuid, secondGuid);
}

Этот тест вызывает одновременно поле Значение Ленивого, и мы хотим утверждать, что оба экземпляра, которые будут возвращены из этого свойства (Значение Ленивого), одинаковы.

Более подробную информацию по этому вопросу можно найти по адресу: C# в глубине

Используйте свои языковые возможности. В основном простая поточно-ориентированная реализация:

public sealed class Singleton
{
    private static readonly Singleton _instance;

    private Singleton() { }

    static Singleton()
    {
        _instance = new Singleton();
    }

    public static Singleton Instance
    {
        get { return _instance; }
    }
}

... что будет самым простым синглтон-классом?

Просто чтобы добавить еще одно возможное решение. Самый простой, самый простой и простой в использовании подход, который я могу придумать, будет примерно таким:

//The abstract singleton
public abstract class Singleton<T> where T : class
{
    private static readonly Lazy<T> instance = new Lazy<T>( CreateInstance, true );

    public static T Instance => instance.Value;

    private static T CreateInstance()
    {
        return (T)Activator.CreateInstance( typeof(T), true);
    }
}

//This is the usage for any class, that should be a singleton
public class MyClass : Singleton<MyClass>
{
    private MyClass()
    {
        //Code...
    }

    //Code...
}

//Example usage of the Singleton
class Program
{
    static void Main(string[] args)
    {
        MyClass clazz = MyClass.Instance;
    }
}

Поэтому, насколько мне известно, это наиболее лаконичная и простая реализация шаблона Singleton в C#.

http://blueonionsoftware.com/blog.aspx?p=c6e72c38-2839-4696-990a-3fbf9b2b0ba4

Я бы, однако, предположил, что синглтоны - это действительно уродливые паттерны... Я считаю их анти-паттернами.

http://blogs.msdn.com/scottdensmore/archive/2004/05/25/140827.aspx

Для меня я предпочитаю иметь что-то вроде репозитория, реализующего IRepository. Ваш класс может объявить зависимость от IRepository в конструкторе, и она может быть передана с использованием Dependency Injection или одного из следующих методов:

http://houseofbilz.com/archive/2009/05/02.aspx

Другие вопросы по тегам