С# два класса со статическими членами, ссылающимися друг на друга
Интересно, почему этот код не заканчивается бесконечной рекурсией. Я предполагаю, что он связан с автоматической инициализацией статических членов значениями по умолчанию, но может ли кто-нибудь сказать мне "шаг за шагом", как "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();
}
Ответы
Ответ 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
Отмените печать и посмотрите, как это происходит другим способом.