Определен ли порядок инициализации статического класса в C #? - PullRequest
31 голосов
/ 10 сентября 2010

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

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 переставлены?

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

Ответы [ 4 ]

37 голосов
/ 10 сентября 2010

Прямо из 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: Возможно, что статические поля с инициализаторами переменных будут соблюдены по умолчаниюзначение состояния.Тем не менее, это настоятельно не рекомендуется как вопрос стиля.

6 голосов
/ 10 сентября 2010

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

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

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

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

4 голосов
/ 27 ноября 2017

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

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
0 голосов
/ 10 сентября 2010

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

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

...