Вот решил посмотреть, что за чудо такое этот Singleton. И сразу возник вопрос: а чем он лучше статик класса? От статик класса объекты нельзя сделать, да и наследоваться от него тоже нельзя. И вообще какова область применения синглтона? В многопоточном приложении будут возникать проблемы при использовании статик класса? Вот, к примеру два одинаковых класса, но реализованных в виде синглтона и статик: public sealed class MyMathSingleton { private static readonly Object obj_lock = new Object() private static MyMathSingleton instance = null private MyMathSingleton() { } public static MyMathSingleton Instance { get { if (instance != null) return instance Monitor.Enter(obj_lock) Interlocked.Exchange(ref instance, new MyMathSingleton()) Monitor.Exit(obj_lock) return instance } } public int A_plus_B(int a, int b) { return a + b } } public static class MyMathStatic { public static int A_plus_B(int a, int b) { return a + b } } PS. реализацию синглтона взял отсюда: Три возраста паттерна Singleton. Правда там автор пишет, что создание инстанса должно выглядеть так, иначе чуда не произойдет: Singleton temp = new Singleton() Interlocked.Exchange(ref instance, temp) А у меня такая запись вполне нормально кушается компилятором: Interlocked.Exchange(ref instance, new MyMathSingleton())
UPD 14.05.27 Допустим у меня в приложении есть форма, с помощью которой в одну из таблиц БД заносится новая запись или же запись редактируется. Эта форма мне нужна только одна, скажу больше - если их будет несколько, то могут возникнуть проблемы при редактировании записи. В данном случае целесообразно реализовать класс формы в виде синглтона?
Ответ Re: ваш PS Автор статьи на Хабре самоуверенно порет чушь. Начнём с главного: его код синглтона не потокобезопасен! Разбор: if(instance != null) return instance // (*) Monitor.Enter(s_lock) Singleton temp = new Singleton() Interlocked.Exchange(ref instance, temp) Monitor.Exit(s_lock) return instance Пусть поток 1 начинает выполнять приведённый код. instance в этот момент есть null, так что первая проверка проходит, и выполнение доходит до строки (*). Пусть в этот момент произойдёт переключение контекста (это ведь возможно, правильно?), и начинает выполняться второй поток. Он точно так же проверяет instance, проходит начальную проверку, пробегает через (*), получает lock, создаёт экземпляр синглтона, записывает его в instance, отпускает lock и выходит. Второй поток получает ссылку на instance только что созданного синглтона. Теперь управление получает первый поток. Он точно так же получает lock, создаёт второй экземпляр синглтона, записывает его в instance, затирая старое значение, отпускает lock и выходит. Первый поток получает ссылку на другой объект. Катастрофа. Теперь по мелочам. Разработчики .NET не просто так сделали lock(obj) синонимом Monitor.Enter(obj, ref lockTaken), а не просто Monitor.Enter(obj). Вариант с Monitor.Enter(obj) работает неправильно в случае, если возможны исключения. Поэтому замена lock на явный Monitor.Enter — ухудшение. Ещё большее ухудшение — отказ от try/catch. Без базара, без try/catch быстрее, только код получается неправильный. (Впрочем, если правильный код нам не нужен, без lock'а было бы ещё быстрее.) Если по каким-то причинам конструктор Singleton'а выбросит исключение (а это может быть не только деление на 0, но и TypeLoadException, например), то s_lock останется залоченным навсегда — deadlock. Затем, автор критикует правильные, рекомендованные экспертами (а Jon Skeet — мегаэксперт) имплементации под совершенно надуманными предлогами. По поводу подхода «через readonly поле» автор пишет: Конструктор может быть только статичным. Это особенность компилятора — если конструткор не статичен, то тип будет помечен как beforefieldinit и readonly создадутся одновременно со static-ами. Это фактически неверно, автор судя по всему просто не понял, что именно написал Jon. У синглтона есть нестатический конструктор, просто явный статический конструктор должен присутствовать. Между «статический конструктор должен присутствовать» и «конструктор может быть только статичным» огромная разница. Правильная, остроумная, каноническая реализация синглтона с вложенным классом удостоилась лишь замечания «Недостатки у него те же самые, что у любого другого кода, который использует nested-классы». Мне неизвестны недостатки вызванные одним лишь наличием вложенных классов. Если мне кто-либо сообщит, готов изменить своё мнение. Наконец, самое концептуально верное решение с Lazy критикуется за то, что оно «не работает с потоками», а используют структуры языка, которые «обманывают интерпретатор». Здесь неверно вообще всё, кроме союзов и предлогов. Начнём с того, что C# — компилируемый язык, интерпретатора C# не существует. Затем, никакого обмана нет: Lazy работает строго как обещает его документация. То, что его поведение отличается от поведения других классов — не обман и не трюк. При желании то же самое можно заимплементировать самому вручную. Затем, правильным при реализации синглтона является не многопоточность или прочие технические мелочи, а простой инвариант: как бы синглтон не использовался, всегда гарантировано наличие не более одного экземпляра синглтона на AppDomain. Именно это гарантируется классом Lazy. Используются ли при этом специальные механизмы для поддержки многопоточности или свойства языка, никому не интересно. Главное — чтобы семантика единственного экземпляра была выдержана. Именно это гарантирует класс Lazy, абстрагируя нас от деталей реализации. И именно поэтому такая имплементация — самая правильная. Вывод: не все статьи на Хабре одинаково полезны.