Подтвердить что ты не робот

С# два класса со статическими членами, ссылающимися друг на друга

Интересно, почему этот код не заканчивается бесконечной рекурсией. Я предполагаю, что он связан с автоматической инициализацией статических членов значениями по умолчанию, но может ли кто-нибудь сказать мне "шаг за шагом", как "a" получает значение 2 и "b" из 1?

public class A
{
    public static int a = B.b + 1;
}
public class B
{
    public static int b = A.a + 1;
}

static void Main(string[] args)
{
    Console.WriteLine("A.a={0}, B.b={1}", A.a, B.b); //A.a=2, B.b=1
    Console.Read();
}
4b9b3361

Ответ 1

Я бы предположил:

  • A.a запрашивается, что заставляет статический инициализатор A запускать
  • Доступ к B.b приводит к тому, что статический инициализатор B запускает
  • A.a запрашивается; инициализатор типа уже активирован (но пока не выполняется присвоение), поэтому поле (еще не назначенное) считывается как 0
  • 0 + 1 - 1, которому присваивается B.b < ==================================== =
  • Теперь мы выходим из B cctor и возвращаемся к A cctor
  • 1 + 1 - 2, которому присваивается A.a < ==================================== =
  • Теперь мы выходим из A cctor
  • 2 возвращается (WriteLine) для A.a
  • мы запрашиваем (на WriteLine) B.b; урок уже выстрелил, поэтому мы видим 1

Ответ 2

Марк действительно прав. Я просто добавлю к его ответу, что на ваш вопрос отвечает раздел 10.5.5.1 спецификации, в котором говорится:

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

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

Например, компилятору jit разрешено указывать "эй, я вижу, что типы A и B впервые используются в этом методе, который вот-вот будет запущен, позвольте мне воспользоваться моментом, чтобы убедиться, что эти типы загружен". Дрожанию разрешено выполнять инициализаторы полей в это время, и он может выбрать сначала A или B сначала по своему усмотрению.

Короче: (1) вы не можете полагаться на это поведение; он определяется реализацией, и (2) спецификация отвечает на ваш точный вопрос; рассмотреть возможность чтения спецификации, когда у вас есть вопрос о семантике языка.

Ответ 3

Он связан с порядком, в котором вы получаете доступ к статическим свойствам. Первым оценивается A.a. При оценке A.a, Bb инициализируется. Поскольку фактическое присвоение a не завершено, значение a остается 0, таким образом, B.b становится равным 1. После инициализации B.b значение может быть присвоено A.a, то есть 1 + 1, таким образом, 2

Ответ 4

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

Ответ 5

Интересно, когда я изменил порядок вывода в вашем примере кода:

    Console.WriteLine("B.b={0} A.a={1}", B.b, A.a);

Я получил противоположные результаты:

B.b=2 A.a=1

Таким образом, похоже, что это связано с тем, к какому приказу они обращаются.

Итак, учитывая, что выход может измениться, добавив раннее использование одной из переменных, похоже, такие рекурсивно определенные значения A BAD IDEA (TM): -)

Ответ 6

Так как A.a ссылается сначала на Console.WriteLine, он загружается первым, что заставляет B загружаться со значением A.a, поскольку 0 = > B.b = 1 = > A.a становится 2

Отмените печать и посмотрите, как это происходит другим способом.