Подтвердить что ты не робот

Как этот контекст окружения становится нулевым?

Может ли кто-нибудь помочь мне объяснить, как TimeProvider.Current может стать нулевым в следующем классе?

public abstract class TimeProvider
{
    private static TimeProvider current =
        DefaultTimeProvider.Instance;

    public static TimeProvider Current
    {
        get { return TimeProvider.current; }
        set
        {
            if (value == null)
            {
                throw new ArgumentNullException("value");
            }
            TimeProvider.current = value;
        }
    }

    public abstract DateTime UtcNow { get; }

    public static void ResetToDefault()
    {
        TimeProvider.current = DefaultTimeProvider.Instance;
    }
}

Наблюдения

  • Все модульные тесты, которые напрямую ссылаются на TimeProvider, также вызывают ResetToDefault() в их отключении Fixture.
  • Не используется многопоточный код.
  • Время от времени один из модульных тестов выходит из строя, потому что TimeProvider.Current имеет значение null (исключение NullReferenceException).
  • Это происходит только при запуске всего пакета, но не тогда, когда я запускаю только один unit test, предлагая мне, что происходит некоторая тонкая тестовая взаимозависимость.
  • Это происходит примерно раз каждые пять или шесть тестовых прогонов.
  • Когда происходит сбой, похоже, это происходит в первых выполненных тестах, которые включают TimeProvider.Current.
  • Более одного теста может потерпеть неудачу, но только один сбой в данном тестовом прогоне.

FWIW, здесь также класс DefaultTimeProvider:

public class DefaultTimeProvider : TimeProvider
{
    private readonly static DefaultTimeProvider instance =
        new DefaultTimeProvider();

    private DefaultTimeProvider() { }

    public override DateTime UtcNow
    {
        get { return DateTime.UtcNow; }
    }

    public static DefaultTimeProvider Instance
    {
        get { return DefaultTimeProvider.instance; }
    }
}

Я подозреваю, что некоторые тонкие взаимодействия происходят со статической инициализацией, где на время выполнения фактически разрешен доступ к TimeProvider.Current до того, как закончится статическая инициализация, но я не могу на нее наложить.

Любая помощь приветствуется.


FWIW Я просто бросил

Console.WriteLine(Thread.CurrentThread.ManagedThreadId);

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

4b9b3361

Ответ 1

Основываясь только на этом коде, Current может быть null на основе того, что он установлен на null. Это явно не поможет вам.

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

В то же время возможно, что статья Jon Skeet, посвященная синглонам, может быть полезна, поскольку DefaultTimeProvider эффективно действует как singleton: http://csharpindepth.com/Articles/General/Singleton.aspx

Ответ 2

У меня может быть частичный ответ на этот вопрос, благодаря ссылкам, предоставленным Питером Ритчи, хотя я не могу полностью объяснить, что происходит. Казалось бы, произошла какая-то гонка между статической инициализацией TimeProvider и DefaultTimeProvider. Это может иметь отношение к beforefieldinit.

Изменение реализации, похоже, решило проблему. Если нет, это, безусловно, сделало условие гонки намного реже, до такой степени, что я еще не видел его.

Я изменил инициализацию TimeProvider на это:

public abstract class TimeProvider
{
    private static TimeProvider current;

    static TimeProvider()
    {
        TimeProvider.current = new DefaultTimeProvider();
    }

    //...
}

И DefaultTimeProvider просто для этого:

public class DefaultTimeProvider : TimeProvider
{
    public override DateTime UtcNow
    {
        get { return DateTime.UtcNow; }
    }
}

В игре есть только один статический инициализатор (TimeProvider), и поскольку он является явным статическим конструктором, класс не помечен beforefieldinit.

Это похоже на трюк...