Неоднозначность в порядке инициализации статических переменных
Во время моего исследования лучшего способа создания 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# гарантирует, что они будут перед этим использованием.
[*] Технически случай, когда это проблематично, может быть динамической инициализацией в стандарте. Инициализация переменных со статической продолжительностью хранения внутри каждой единицы преобразования представляет собой двухэтапный процесс, в котором во время первого прохода статическая инициализация устанавливает переменные в фиксированное константное выражение, а затем во втором проходе, называемом динамической инициализацией, все переменные со статическим хранением имеют инициализатор не является константным выражением, инициализируются.