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

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

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

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

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

Так что я нашел эту страницу, где они говорят

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

и привести пример

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) должен выполняться перед статическим конструктором A и инициализаторами поля".

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

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

2 ответа

Решение

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

Это означает, что внутри одного и того же класса статические поля инициализируются в порядке появления в исходном коде. Например:

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) должен выполняться перед статическим конструктором A и инициализаторами поля".

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

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

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


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

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

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

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

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


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

Другие вопросы по тегам