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

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

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

Я изучил вопрос и нашел this и это. Где в основном суть (насколько я понимаю) в том, что порядок инициализации статических переменных в 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: полностью не зависит от того, где - или порядок - объявления класса. Тем не менее, согласно моей интерпретации этой статьи, в нем говорится, что это результат порядка объявления тех классов, которые мое тестирование не поддерживает?

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


person Maxim Gershkovich    schedule 09.07.2012    source источник


Ответы (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.

person Jon    schedule 09.07.2012
comment
Большое спасибо за предельно лаконичный ответ. - person Maxim Gershkovich; 11.07.2012

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

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

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

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


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

person David Rodríguez - dribeas    schedule 09.07.2012
comment
+1 и придирка: технически, если тип украшен beforefieldinit, тогда CLR не требуется для инициализации статических членов до тех пор, пока к ним не будет осуществлен доступ (например, вы можете вызывать статические методы сколько угодно). Практически он инициализирует поля еще до обращения к классу. - person Jon; 09.07.2012