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

Является ли порядок инициализации статического класса в С# детерминированным?

Я провел некоторый поиск и думаю, что следующий код гарантированно выдаст результат:

B.X = 7

B.X = 0

A.X = 1

A = 1, B = 0
static class B
{
    public static int X = 7;
    static B() {
        Console.WriteLine("B.X = " + X);
        X = A.X;
        Console.WriteLine("B.X = " + X);
    }
}

static class A
{
    public static int X = B.X + 1;
    static A() {
        Console.WriteLine("A.X = " + X);
    }
}

static class Program
{
    static void Main() {
        Console.WriteLine("A = {0}, B = {1}", A.X, B.X);
    }
}

Я запускал это много раз и всегда получал результат выше раздела кода; Я просто хотел проверить, изменится ли это? Даже если в тексте класс A и класс B переставлены?

Гарантируется ли, что первое использование статического объекта вызовет инициализацию его статических членов, а затем создание его статического конструктора? Для этой программы использование AX в основном вызовет инициализацию AX, которая, в свою очередь, инициализирует BX, затем B() и после завершения инициализации AX перейдет к A(). Наконец, Main() выведет AX и B.X '.

4b9b3361

Ответ 1

Прямо от ECMA-334:

17.4.5.1: "Если в классе существует статический конструктор (§17.11), выполнение инициализаторов статического поля происходит непосредственно перед выполнением этого статического конструктора. В противном случае инициализаторы статического поля выполненный в зависящее от реализации время до первого использования статического поля этого класса.

и

17.11: Выполнение статического конструктора запускается первым из следующих событий, которые должны произойти в домене приложения:

  • Создается экземпляр класса.
  • Ссылка на любой из статических членов класса.

Если класс содержит метод Main (§10.1), в котором начинается выполнение, статический конструктор для этого класса выполняется до вызова метода Main. Если класс содержит любые статические поля с инициализаторами, то инициализаторы выполняются в текстовом порядке непосредственно перед выполнением статического конструктора (§17.4.5).

Итак, порядок:

  • A.X, поэтому static A() вызывается.
  • A.X должен быть инициализирован, но он использует B.X, поэтому static B() вызывается.
  • B.X должен быть инициализирован и инициализирован 7. B.X = 7
  • Все статические поля B инициализируются, поэтому вызывается static B(). X печатается ( "7" ), затем устанавливается значение A.X. A уже начал инициализироваться, поэтому мы получаем значение A.X, которое является значением по умолчанию ( "когда инициализируется класс, все статические поля в этом классе сначала инициализируются значением по умолчанию" ); B.X = 0, и печатается ( "0" ).
  • Готово инициализировать B, а значение A.X установлено на B.X+1. A.X = 1.
  • Все статические поля A инициализируются, поэтому вызывается static A(). A.X печатается ( "1" ).
  • Вернувшись в Main, печатаются значения A.X и B.X ( "1", "0" ).

Он действительно комментирует это в стандарте:

17.4.5: Статические поля с переменными инициализаторами можно наблюдать в их состоянии значения по умолчанию. Тем не менее, это сильно обескураживается как вопрос стиля.

Ответ 2

В выполнении этой гарантии участвуют четыре разных правила в спецификации С#, и это относится к С#. Единственная гарантия, выполняемая средой выполнения .NET, заключается в том, что инициализация типа начинается до использования типа.

  • Статические поля инициализируются нулями до тех пор, пока не будет запущен инициализатор типа.
  • Эти статические инициализаторы полей запускаются непосредственно перед статическим конструктором.
  • Эти статические конструкторы вызываются при вызове конструктора первого экземпляра или первой статической ссылки на элемент.
  • Эти аргументы функции оцениваются в порядке слева направо.

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

Обратите внимание, что комментарий Porges был связан с моим первоначальным выражением (на основе поведения .NET), что гарантии слишком слабы, чтобы гарантировать наблюдаемое поведение. Порсес прав, что гарантии достаточно сильны, но на самом деле участвует гораздо более сложная цепь, чем он предлагает.

Ответ 3

Возможно, вам будет интересно узнать, что можно даже присвоить значения полю между его инициализацией по умолчанию и инициализацией переменной.

private static int b = Foo();
private static int a = 4;

private static int Foo()
{
    Console.WriteLine("{0} - Default initialization", a);
    a = 3;
    Console.WriteLine("{0} - Assignment", a);
    return 0;
}

public static void Main()
{
    Console.WriteLine("{0} - Variable initialization", a);
}

выходы

0 - Default initialization
3 - Assignment
4 - Variable initialization

Ответ 4

Детерминированная инициализация статических членов действительно гарантирована... но это не в "текстовом порядке". Кроме того, это может быть выполнено не полностью ленивым способом (что означает только тогда, когда статическая переменная первая ссылка). Однако в вашем примере с использованием целых чисел это не повлияет.

В некоторых случаях желательно получить ленивую инициализацию (особенно с дорогими синглтонами) - в этом случае вам иногда приходится перепрыгнуть через некоторые обручи, чтобы понять это.