Как отложить статическую инициализацию внутри свойства - PullRequest
0 голосов
/ 18 июня 2010

Я создал класс, который представляет собой нечто среднее между синглтоном (пятая версия) и фабрикой (зависимая инъекция). Назовите это "Моно-фабрикой?" Это работает и выглядит так:

public static class Context
{
    public static BaseLogger LogObject = null;

    public static BaseLogger Log
    {
        get
        {
            return LogFactory.instance;
        }
    }

    class LogFactory
    {
        static LogFactory() { }
        internal static readonly BaseLogger instance = LogObject ?? new BaseLogger(null, null, null);
    }
}

//USAGE EXAMPLE:
//Optional initialization, done once when the application launches...
Context.LogObject = new ConLogger();

//Example invocation used throughout the rest of code...
Context.Log.Write("hello", LogSeverity.Information);

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

public static class Context
{
    private static BaseLogger LogObject = null;

    public static BaseLogger Log
    {
        get
        {
            return LogFactory.instance;
        }
        set
        {
            LogObject = value;
        }
    }

    class LogFactory
    {
        static LogFactory() { }
        internal static readonly BaseLogger instance = LogObject ?? new BaseLogger(null, null, null);
    }
}

Вышеприведенное не работает, потому что в момент касания свойства Log (посредством вызова метода set) это приводит к выполнению пути кода, связанного с методом получения ... что означает, что внутренние данные экземпляра LogFactory всегда устанавливаются в BaseLogger (установка «LogObject» всегда слишком поздно!).

Так есть ли декорация или другой прием, который я могу использовать, который приведет к тому, что путь get для свойства Log будет ленивым во время вызова заданного пути?

Ответы [ 2 ]

3 голосов
/ 18 июня 2010

Примечание: Это полное переписывание исходного ответа;однако рекомендация остается в силе.

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

В .NET 4 ваш второй пример работает, и я, честно говоря, ожидаю , что он будет работать и в .NET 2.Пока вы не коснетесь свойства Context.Log или LogFactory.instance случайно.Тем не менее, он выглядит ужасно хрупким.

Кроме того, строго говоря, тонкости beforefieldinit, которые вы здесь пытаетесь использовать, могут укусить вас в многопоточном приложении: инициализация LogFactory не требуетработать в том же потоке, что и установщик Context.Log[Object].Это означает, что при инициализации LogFactory.instance в этот thread Context.LogObject еще не нужно устанавливать, а в другом (такие синхронизации могут выполняться лениво).Так что не потокобезопасен.Вы можете попытаться исправить это, сделав Context.LogObject volatile , таким образом, набор будет виден во всех потоках одновременно.Но кто знает, в какие другие условия гонки мы попадаем в следующие.

И после всех трюков у вас все еще остается довольно неинтуитивный результат:

Context.Log = value1; // OK
Context.Log = value2; // IGNORED

Вы ожидаетеВторой вызов сеттера для работы (Context.Log == value2) или для броска.Не игнорировать молча.

Вы также можете пойти на

public static class Context
{
    private static BaseLogger LogObject;

    public static BaseLogger Log
    {
        get { return LogObject ?? LogFactory.instance; }
        set { LogObject = value; }
    }

    private class LogFactory
    {
        static LogFactory() {}
        internal static readonly BaseLogger instance 
               = new BaseLogger(null, null, null);
    }
}

Здесь результат гарантирован и ленив (в соответствии с пятым синглтон-методом Джона Скита),И выглядит ИМХО намного чище.

0 голосов
/ 18 июня 2010

Несколько подсказок:

Извлечение Обобщения

Я избегаю инициализации с использованием статического.Это может вызвать странные проблемы на практике.Например, если то, что вы создаете, выдает ошибку, тогда загрузчик Windows скажет вам, что есть проблема, но не скажет, что именно.Ваш код фактически никогда не вызывается, поэтому у вас нет шансов на исключение для решения проблемы.Я создаю первый экземпляр, когда он используется в первый раз.Вот пример:

    private static OrderCompletion instance;

    /// <summary>
    /// Get the single instance of the object
    /// </summary>
    public static OrderCompletion Instance
    {
        get
        {
            lock (typeof(OrderCompletion))
            {
                if (instance == null)
                    instance = new OrderCompletion();
            }
            return instance;
        }
    }
...