Статическое поле Инициализация в отладочной и выпускной сборке

Я обнаружил, что статическая инициализация поля может вести себя по-разному. для следующего кода,

public class Class1
{
  public static void Main()
  {
    Console.WriteLine("Main");
    Test();
    Console.ReadLine();
  }

  public static void Test(){
    Console.WriteLine("Test");
    Singleton.Instance.DoSomething();
  }
}

public class Singleton
{
  private static Singleton sInstance = new Singleton();

  protected Singleton()
  {
    Console.WriteLine("Singleton Constructor");
  }

  public static Singleton Instance
  {
    get
    {
      return sInstance;
    }
  }

  public void DoSomething(){}
}

в сборке отладки, он напечатает

Main
Test
Singleton Constructor

в то время как в сборке релизов он будет печатать

Main
Singleton Constructor
Test

Я проверил код IL, сгенерированный этими 2 строками, есть почти то же самое.

Интересно, как это происходит? И если это какая-то оптимизация JIT в сборке релизов, какова мотивация?

Ответ 1

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

Из MSDN

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

Добавьте статический конструктор в класс Singleton

static Singleton() { }

Ответ 2

Я воспроизвожу джиттер x86 в сборке релизов. Это по дизайну, нет гарантированного точного момента, когда работает статический конструктор классов, единственным требованием является то, что это происходит до того, как будут запущены другие методы в классе. Поэтому оптимизатору разрешено переупорядочивать код, если он создает более эффективный код. Это происходит, он переводит вызов cctor обратно в метод Main(). Преимущество в том, что теперь у него больше возможностей для оптимизации оставшегося кода.

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

Ответ 3

В еще один ответ, я предоставляю тестовую программу и обсуждение сравнения между {Debug, Release}, {x86, x64} и .NET. Итоговые результаты заключаются в следующем: для получения полной информации, проверьте ссылку :

.NET 2.0/3.5

2.0.50727.8825 x86 Debug
2.0.50727.8825 x86 Debug TouchMe fieldinit
2.0.50727.8825 x86 Release fieldinit
2.0.50727.8825 x86 Release TouchMe fieldinit
2.0.50727.8825 x64 Debug
2.0.50727.8825 x64 Debug TouchMe fieldinit
2.0.50727.8825 x64 Release
2.0.50727.8825 x64 Release TouchMe fieldinit

.NET 4.7.1

4.7.2556.0 x86 Debug
4.7.2556.0 x86 Debug TouchMe fieldinit
4.7.2556.0 x86 Release
4.7.2556.0 x86 Release TouchMe
4.7.2556.0 x64 Debug
4.7.2556.0 x64 Debug TouchMe fieldinit
4.7.2556.0 x64 Release
4.7.2556.0 x64 Release TouchMe