Статический инициализатор C# с (и без) смешанными статическими конструкторами
Я прошел через соответствующий раздел C# Language Spec (v5.0), но я не могу найти кусок, который имеет отношение к тому, что я вижу.
Если у вас есть прогон кода ниже, вы увидите вывод ниже, что я и ожидаю:
using System;
class Test {
static int count = 0;
static void Main() {
Console.WriteLine("In Main(), A.X=" + A.X);
}
public static int F(string message) {
Console.WriteLine(message);
A.X = ++count;
Console.WriteLine("\tA.X has been set to " + A.X);
B.Y = ++count;
Console.WriteLine("\tB.Y has been set to " + B.Y);
return 999;
}
}
class A {
static A() { }
public static int U = Test.F("Init A.U");
public static int X = Test.F("Init A.X");
}
class B {
static B() { }
public static int R = Test.F("Init B.R");
public static int Y = Test.F("Init B.Y");
}
Выход:
Init A.U
A.X has been set to 1
Init B.R
A.X has been set to 3
B.Y has been set to 4
Init B.Y
A.X has been set to 5
B.Y has been set to 6
B.Y has been set to 2
Init A.X
A.X has been set to 7
B.Y has been set to 8
In Main(), A.X=999
Это именно тот результат, который я ожидаю. В частности, обратите внимание, что, хотя метод F() выполняется с параметром "Init AU", он вызывается снова (прерывается, если хотите), когда встречается ссылка на BY, вызывая выполнение статических инициализаторов B. Как только статический конструктор B завершится, мы снова вернемся к вызову AU для F(), в котором для BY задано значение 6, а затем 2. Таким образом, мы надеемся, что этот вывод имеет смысл для всех.
Вот что я не понимаю: если вы закомментируете статический конструктор B, это вывод, который вы видите:
Init B.R
A.X has been set to 1
B.Y has been set to 2
Init B.Y
A.X has been set to 3
B.Y has been set to 4
Init A.U
A.X has been set to 5
B.Y has been set to 6
Init A.X
A.X has been set to 7
B.Y has been set to 8
In Main(), A.X=999
В разделах 10.5.5.1 и 10.12 спецификации C# (v5.0) указывается, что статический конструктор A (и его статические инициализаторы) запускаются при выполнении "ссылки на любой из статических членов класса". Тем не менее, здесь у нас есть AX, на который ссылаются из F(), и статический конструктор A не запускается (поскольку его статические инициализаторы не работают).
Так как A имеет статический конструктор, я ожидал бы, что эти инициализаторы будут запускать (и прерывать) вызов "Init BR" для F(), так же как статический конструктор B прервал вызов A для F() в вызове "Init AU", который я показал в начале.
Кто-нибудь может объяснить? На первый взгляд, это выглядит как нарушение спецификации, если только нет какой-то другой части спецификации, которая позволяет это.
Спасибо
1 ответ
Я думаю, что вижу, что здесь происходит, хотя у меня нет хорошего объяснения, почему это так.
Тестовая программа слишком грубая, чтобы увидеть, что происходит. Давайте сделаем небольшую корректировку:
class Test {
static int count = 0;
static void Main() {
Console.WriteLine("In Main(), A.X=" + A.X);
}
public static int F(string message) {
Console.WriteLine("Before " + message);
return FInternal(message);
}
private static int FInternal(string message) {
Console.WriteLine("Inside " + message);
A.X = ++count;
Console.WriteLine("\tA.X has been set to " + A.X);
B.Y = ++count;
Console.WriteLine("\tB.Y has been set to " + B.Y);
return 999;
}
}
class A {
static A() { }
public static int U = Test.F("Init A.U");
public static int X = Test.F("Init A.X");
}
class B {
static B() { }
public static int R = Test.F("Init B.R");
public static int Y = Test.F("Init B.Y");
}
Вывод аналогичен описанному в вопросе, но более подробно:
Before Init A.U
Inside Init A.U
A.X has been set to 1
Before Init B.R
Inside Init B.R
A.X has been set to 3
B.Y has been set to 4
Before Init B.Y
Inside Init B.Y
A.X has been set to 5
B.Y has been set to 6
B.Y has been set to 2
Before Init A.X
Inside Init A.X
A.X has been set to 7
B.Y has been set to 8
In Main(), A.X=999
Ничего удивительного здесь. Удалите статический конструктор B, и вот что вы получите:
Before Init A.U
Before Init B.R
Inside Init B.R
A.X has been set to 1
B.Y has been set to 2
Before Init B.Y
Inside Init B.Y
A.X has been set to 3
B.Y has been set to 4
Inside Init A.U
A.X has been set to 5
B.Y has been set to 6
Before Init A.X
Inside Init A.X
A.X has been set to 7
B.Y has been set to 8
In Main(), A.X=999
Теперь это интересно. Мы видим, что первоначальный вывод вводил в заблуждение. На самом деле мы начинаем с попытки инициализации A.U
, Это не удивительно, потому что A должен быть инициализирован первым, потому что A.X
Доступ к Main. Следующая часть интересная. Похоже, что когда B не имеет статического конструктора, CLR прерывает метод, который собирается получить доступ к полям B (FInternal
) прежде чем он войдет в метод. Сравните это с другим случаем. Там инициализация B была отложена до тех пор, пока мы фактически не получили доступ к полям B.
Я не совсем уверен, почему все делается именно в этом порядке, но вы можете видеть, что причина инициализации B не прерывается для инициализации A в том, что инициализация A уже началась.