Шаблон C # Singleton с триггерной инициализацией - PullRequest
7 голосов
/ 04 августа 2011

Мне нужен синглтон, который:

  • лениво загружен
  • потокобезопасен
  • загружает некоторые значения при строительстве
  • эти значения могут быть запрошены в любое время
  • инициализация МОЖЕТ произойти в какое-то точное время, прежде чем начнутся запросы - поэтому я должен иметь возможность вызывать его извне. Конечно, запуск несколько раз должен выполнить инициализацию только один раз.

Я использую .NET 3.5.

Я начал с реализации Джона Скита (5-я версия) с использованием статического подкласса:

public sealed class Singleton
{
    IEnumerable<string> Values {get; private set;}
    private Singleton()
    {
        Values = new[]{"quick", "brown", "fox"};
    }

    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();
    }
} 

Это помечает почти все поля, кроме «инициализации триггера извне». Поскольку фактическая инициализация происходит внутри ctor, она не может происходить более одного раза.

Как это можно сделать?

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

public static void Main(){

    //do stuff, singleton should not yet be initialized.

    //the time comes to initialize the singleton, e.g. a database connection is available
    //this may be called 0 or more times, possibly on different threads

    Singleton.Initialize();
    Singleton.Initialize();
    Singleton.Initialize();

    //actual call to get retrieved values, should work
    var retrieveVals = Singleton.Instance.Values;

}

Ответы [ 6 ]

3 голосов
/ 04 августа 2011

Похоже, что вы могли бы сделать:

public sealed class Singleton
{
    IEnumerable<string> Values {get; private set;}
    private Singleton(bool loadDefaults)
    {
        if (loadDefaults)
            Values = new[]{"quick", "brown", "fox"};
        else
            Values = new[]{"another", "set", "of", "values"};
    }

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

    public static void Initialize() {
        Nested.Initialize();
    }

    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(true);
        private static object instanceLock = new object();
        private static bool isInitialized = false; 

        public static void Initialize() {
            lock(instanceLock) {
                if (!isInitialized) {
                    isInitialized = true;
                    instance = new Singleton(false);
                }
            }
        }

    }
} 

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

public sealed class Singleton
{
    IEnumerable<string> Values {get; private set;}
    private Singleton()
    {
        Values = new[]{"quick", "brown", "fox"};
    }

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

    private static object instanceLock = new object();
    private static bool isInitialized = false; 

    public static void Initialize() {
        lock(instanceLock) {
            if (!isInitialized) {
                isInitialized = true;
                Instance.Values = new[]{"another", "set", "of", "values"};
            }
        }
    }

    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();
    }
} 

И третий вариант, основанный на вашем неизменном комментарии и удаленииВложенный комментарий класса:

public sealed class Singleton
{
    IEnumerable<string> Values {get; private set;}
    private Singleton()
    {
        Values = new[]{"quick", "brown", "fox"};
    }

    private static Singleton instance;
    private static object instanceLock = new object();

    public static Singleton Instance {
        get {
            Initialize();
            return instance;
        }
     }

    public static void Initialize() {
        if (instance == null) {
            lock(instanceLock) {
                if (instance == null)
                    instance = new Singleton();
            }
        }
    }
} 
1 голос
/ 04 августа 2011

Первая идея, которая у меня возникла, состояла в том, чтобы просто использовать одноразовую переменную, назначенную экземпляру синглтона, которая (вероятно?) Вызовет инициализацию

static Main() 
{
    var unused = Singleton.Instance;
    //this should initialize the singleton, unless the compiler optimizes it out.
    //I wonder if the compiler is smart enough to see this call has side effects.

    var vals = Singleton.Instance.Values;
}

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

public class Singleton {
    public static void Initialize() {
        //this accesses the static field of the inner class which triggers the private Singleton() ctor.  
        Instance._Initialize();
    }
    private void _Initialize()
    { //do nothing
    }

    [the rest as before]
}

, поэтому использование будет:

static Main() 
{
    //still wondering if the compiler might optimize this call out
    Singleton.Initialize();

    var vals = Singleton.Instance.Values;
}

Кстати, это также сработает:

static Main() 
{
    var vals = Singleton.Instance.Values;
}

За исключением оптимизации компилятора, я думаю, что это отвечает всем требованиям.

0 голосов
/ 04 августа 2011
public class Singleton<T> where T : class, new()
{
    private static T instance;
    public static T Instance
    {
        get
        {
            if (instance == null)
            {
                throw new Exception("singleton needs to be initialised before use");
            }
            return instance;
        }
    }
    public static void Initialise(Action<T> initialisationAction)
    {
        lock(typeof(Singleton<T>))
        {
            if (instance != null)
            {
                return;
            }
            instance = new T();
            initialisationAction(instance);
        }
    }
}
0 голосов
/ 04 августа 2011

Вы можете сделать что-то вроде этого

public sealed class Singleton
{
    IEnumerable<string> Values { get; set; }

    private Singleton()
    {
        Console.WriteLine("-- Private Singleton constructor");
        Values = new[] { "quick", "brown", "fox" };
    }

    public static Singleton Instance
    {
        get
        {
            Console.WriteLine("- Singleton Instance");
            return Nested.instance;
        }
    }

    public static void Initialize()
    {
        Console.WriteLine("- Singleton Initialize");
        Nested.Initialize();
    }

    internal class Nested
    {
        private static object syncRoot = new object();
        // Explicit static constructor to tell C# compiler
        // not to mark type as beforefieldinit
        static Nested()
        {
            Console.WriteLine("-- Static Nested constructor");
        }

        internal static readonly Singleton instance = new Singleton();

        internal static void Initialize()
        {
            lock (syncRoot)
            {
                Console.WriteLine("-- Locked");
                Console.WriteLine("--- Nested Initialize");
                Console.WriteLine("-- Unlocked");
            }
        }
    }
}

Использование

class Program
{
    static void Main(string[] args)
    {
        var i = Singleton.Instance;
        i = Singleton.Instance;

        Console.WriteLine("-----");

        Singleton.Initialize();
        Singleton.Initialize();
        Singleton.Initialize();

        Console.Read();
    }
}

Какие выходы

- Singleton Instance
-- Private Singleton constructor
-- Static Nested constructor
- Singleton Instance
-----
- Singleton Initialize
-- Locked
--- Nested Initialize
-- Unlocked
- Singleton Initialize
-- Locked
--- Nested Initialize
-- Unlocked
- Singleton Initialize
-- Locked
--- Nested Initialize
-- Unlocked
0 голосов
/ 04 августа 2011

Вы можете использовать дважды проверенную схему блокировки.Просто добавьте следующий код в ваш класс Singleton:

public sealed class Singleton
{
   ..........................

        private static object locker = new object();
        private static bool initialized = false;

        public static void Initialize() {
           if (!initialized){ 
             lock(locker) {
                if (!initialized){ 
                  //write initialization logic here
                  initialized = true;
                 }
              }
            }
        }

.......................

}
0 голосов
/ 04 августа 2011

Вы можете настроить метод Initialize, который может быть запущен извне, если вам нужно, чтобы инициализация произошла позже, но если значения отличаются при каждом запуске, тогда он не может быть статическим, что нарушает шаблон Singleton .

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

Я не уверен, что вам нужна имплементация только для Singleton, но я не могу полностью ответить без информации о том, выполняет ли Initialize () каждый раз один и тот же код или имеет какой-то тип переменной природы.

...