Неоднозначность порядка инициализации статических переменных


Во время моего исследования наилучшего способа построения синглтона в C# я наткнулся на следующую статью, где есть краткое упоминание о том, что в C++

"спецификация C++ оставила некоторую неопределенность вокруг инициализации порядок статических переменных."

В конце концов я изучил вопрос и нашел это и это. Где в основном суть (насколько я понимаю) заключается в том, что порядок инициализации статических переменных в C++ не определено. Хорошо, я думаю, что до сих пор так хорошо, но затем я хотел понять следующее утверждение, которое статья позже делает

" К счастью, платформа .NET Framework разрешает эту двусмысленность с помощью своей обработка инициализации переменной."

Итак, я нашел ЭТУ страницу, где говорится

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

И приведем пример

using System;
class Test
{
   static void Main() {
      Console.WriteLine("{0} {1}", B.Y, A.X);
   }
   public static int F(string s) {
      Console.WriteLine(s);
      return 1;
   }
}
class A
{
   static A() {}
   public static int X = Test.F("Init A");
}
class B
{
   static B() {}
   public static int Y = Test.F("Init B");
}

the output must be: 
Init B 
Init A
1 1

" потому что правила для выполнения статических конструкторов (как определено в Раздел 10.11) обеспечивают, что статический конструктор B (и, следовательно, B статические инициализаторы полей) должны выполняться перед статический конструктор и инициализаторы полей."

Но где я запутался, так это то, что мое понимание состояло в том, что порядок инициализации статических переменных в этих примерах будет основан на том, когда метод или сначала вызывалось поле внутри класса, которое, в свою очередь, основано на порядке выполнения блока кода (в данном случае слева направо). Т. е.: полностью независимо от того, где - или порядок-объявления класса. Тем не менее, моя интерпретация этой статьи говорит, что она является результатом порядка объявления тех классов, которые мое тестирование не поддерживает?

Не мог бы кто-нибудь разъяснить это (и то, что пытается сделать статья) для меня и, возможно, привести лучший пример что неграмотно описанное поведение?

2 5

2 ответа:

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

Это означает, что внутри одного и того же класса статические поля инициализируются в порядке появления в исходном коде. Например:
class A
{
   public static int X = Test.F("Init A.X");
   public static int Y = Test.F("Init A.Y");
}

Когда наступает время инициализации статических полей, X гарантированно инициализируется до Y.

" потому что правила для выполнения статических конструкторов (как определено в Раздел 10.11) обеспечивают, что статический конструктор B (и, следовательно, B статические инициализаторы полей) должны выполняться перед статический конструктор и инициализаторы полей."

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

static void Main() {
    Console.WriteLine("{0} {1}", B.Y, A.X);
}

Предполагая, что ни A, ни B не были уже статически инициализированы, порядок вычисления гарантирует, что все поля B будут инициализированы перед любым полем A. Поля каждого класса будут инициализированы в порядке, указанном первым правилом.


1 для целей настоящего обсуждения я игнорирую существование beforefieldinit.

В языке C++ порядок инициализации переменных со статической длительностью хранения в одной единице трансляции - это порядок, в котором происходят определения таких переменных. Не определено, каков порядок инициализации переменных со статической длительностью хранения в различных единицах трансляции.

То есть стандарт C++ действительно предлагает аналогичную гарантию тому, что вы процитировали, подставляя порядок объявления в классе вместо порядка определения в единственном единица перевода, определяющая такие переменные. Но это не самое главное различие.

В то время как в C++ это единственная гарантия, в C# есть дополнительная гарантия того, что все статические члены будут инициализированы перед первым использованием класса. Это означает, что, если ваша программа зависит от A (рассмотрим каждый тип в другой сборке, которая является наихудшим случаем), она начнет инициализацию всех статических полей в A, Если A в свою очередь зависит от B для любого из этих статических полей. инициализации, то инициализация B статических членов будет инициализирована там.

Сравните это с C++, где во время статической инициализации[*], все остальные переменные со статической длительностью считаются инициализированными. Это главное отличие: C++ предполагает, что они инициализированы, C# гарантирует, что они находятся перед этим использованием.


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