Реализация шаблона Singleton в C #
Это лучший способ реализовать этот шаблон в C #?
public sealed class Singleton
{
private static readonly Singleton instance = new Singleton();
public static Singleton Instance { get { return instance; } }
static Singleton() {}
private Singleton() {}
}
9 ответов
Я использую версию Jon Skeet потокового безопасного синглтона с полностью ленивым экземпляром в C #:
public sealed class Singleton
{
// Thread safe Singleton with fully lazy instantiation á la Jon Skeet:
// http://csharpindepth.com/Articles/General/Singleton.aspx
Singleton()
{
}
public static Singleton Instance
{
get
{
return Nested.instance;
}
}
class Nested
{
// Explicit static constructor to tell C# compiler
// not to mark type as beforefieldinit
static Nested()
{
}
internal static readonly Singleton instance = new Singleton();
}
}
Это творит чудеса для меня! Это очень просто в использовании, просто получите экземпляр из свойства Instance; SingletonName instance = SingletonName.Instance;
Я всегда использую это, он позволяет ленивую инициализацию дженериков, вместо того, чтобы создавать новый одноэлементный класс для каждого типа singleton, который я хочу. Он также является потокобезопасным, но не использует блокировки при каждом доступе.
public static class Singleton<T> where T : class, new()
{
private T instance = null;
public T Instance
{
get
{
if (instance == null)
Interlocked.CompareExchange(ref instance, new T(), null);
return instance;
}
}
}
Если вы не знакомы с блокированным классом, он выполняет атомарные операции таким образом, который часто быстрее, чем блокировка. Три возможных случая:
1) Первый доступ одним потоком . Вероятно, примерно такая же производительность, как и очевидный метод с использованием блокировки
2) Первый доступ по многим темам одновременно . Многие потоки могут войти в блокированный обмен, и в этом случае можно создать несколько элементов, но только 1 будет «выигрывать». Пока ваш конструктор не имеет глобальных побочных эффектов (что действительно не должно), поведение будет правильным. Производительность будет немного меньше блокировки из-за множества распределений, но накладные расходы небольшие, и это очень редкий случай.
3) Позднее вызывается . Никаких операций блокировки или блокировки, это довольно оптимально и, очевидно, является большинством.
Если вы используете .NET 4.0, вы можете воспользоваться System.Lazy класс. Он гарантирует, что экземпляр создается только один раз.
Я использую шаблон, аналогичный уже опубликованному, но со следующей разницей:
public sealed class Logging
{
static Logging instance = null;
static readonly object lockObj = new object();
private Logging()
{
}
public static Logging Logger
{
get
{
**if (instance == null)**
{
lock (lockObj)
{
if (instance == null)
{
instance = new Logging();
}
}
}
return instance;
}
}
}
Причина в том, что он каждый раз избегает блокировки вызовов, что может помочь вам в производительности. Таким образом, вы проверяете значение null, а затем, если оно равно null, заблокируйте его. Затем вам нужно снова проверить значение null (потому что кто-то, возможно, пришел в эту секунду), но тогда вы должны быть в порядке. Таким образом, вы впервые нажмете блокировку в первый раз (или два) и пропустите его, не блокируя оставшуюся часть времени.
Я всегда использую это решение, если мне нужно реализовать Singleton в C #
public sealed class Logging
{
static Logging instance = null;
static readonly object lockObj = new object();
private Logging()
{
}
public static Logging Logger
{
get
{
lock (lockObj)
{
if (instance == null)
{
instance = new Logging();
}
return instance;
}
}
}
}
По сравнению с вашим решением это потокобезопасно и использует метод ленивого создания. (Объект создается только при необходимости)
Желание против ленивого создания на самом деле не стоит обсуждать в этом примере, но я по возможности использовал бы потокобезопасную реализацию.
Зависимость Контейнеры для инъекций, такие как Unity, поддерживают концепцию singleton. Unity от Microsoft (и с открытым исходным кодом), но есть много контейнеров DI с открытым исходным кодом .
Вот пример из страницы блокировки с двойной проверкой Wikipedia:
public class MySingleton
{
private static object myLock = new object();
private static MySingleton mySingleton = null;
private MySingleton()
{ }
public static MySingleton GetInstance()
{
if (mySingleton == null) // check
{
lock (myLock)
{
if (mySingleton == null) // double check
{
MySingleton newSingleton = new MySingleton();
System.Threading.Thread.MemoryBarrier();
mySingleton = newSingleton;
}
}
}
return mySingleton;
}
}
Обратите внимание на использование System.Threading.Thread.MemoryBarrier();
, в отличие от ответа GWLlosa. См. Объявление с двойной проверкой блокировки для объяснение. (Он написан для Java, но применяются те же принципы.)
Вот реализация, которая использует Lazy<T>
:
public class Foo : IFoo
{
private static readonly Lazy<IFoo> __instance
= new Lazy<IFoo>(valueFactory: () => new Foo(), isThreadSafe: true);
private Foo() { }
public static IFoo Instance { get { return __instance.Value; } }
}
Простой способ предложить простой способ использования ключевого слова volatile, статическая переменная не является потокобезопасной, но атомное исполнение считается потокобезопасным. используя volatile, мы можем обеспечить работу атома.
public sealed class Singleton
{
private static **volatile** readonly Singleton instance = new Singleton();
public static Singleton Instance { get { return instance; } }
private Singleton() { }
}
Блокировка - это не верный путь, так как это дорого.
Мне нравится john skeet способ создания одноэлементного способа создания объектов.