C # два класса со статическими членами, ссылающимися друг на друга - PullRequest
18 голосов
/ 07 мая 2010

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

Ответы [ 6 ]

16 голосов
/ 07 мая 2010

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

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

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

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

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

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

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

6 голосов
/ 07 мая 2010

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

2 голосов
/ 07 мая 2010

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

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

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

B.b=2 A.a=1

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

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

2 голосов
/ 07 мая 2010

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

1 голос
/ 07 мая 2010

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

Переверните отпечаток и посмотрите, как это происходит в другом направлении.

...