Порядок статических конструкторов/инициализаторов в С#
Во время работы над приложением С# я заметил, что в нескольких местах статические инициализаторы имеют зависимости друг от друга следующим образом:
static private List<int> a = new List<int>() { 0 };
static private List<int> b = new List<int>() { a[0] };
Не делай ничего особенного, что сработало. Это просто удача? Есть ли у С# правила для решения этой проблемы?
Изменить: (re: Panos) В файле лексический порядок кажется королем? как насчет файлов?
В поиске я пробовал циклическую зависимость, подобную этой:
static private List<int> a = new List<int>() { b[0] };
static private List<int> b = new List<int>() { a[0] };
и программа не работала одинаково (тест не удался по всей доске, и я не смотрел дальше).
Ответы
Ответ 1
Кажется, что это зависит от последовательности строк. Этот код работает:
static private List<int> a = new List<int>() { 1 };
static private List<int> b = new List<int>() { a[0] };
пока этот код не работает (он выбрасывает NullReferenceException
)
static private List<int> a = new List<int>() { b[0] };
static private List<int> b = new List<int>() { 1 };
Таким образом, очевидно, что нет никаких правил для циклической зависимости. Это, однако, то, что компилятор не жалуется...
РЕДАКТИРОВАТЬ - Что происходит "через файлы"? Если мы объявим эти два класса:
public class A {
public static List<int> a = new List<int>() { B.b[0] };
}
public class B {
public static List<int> b = new List<int>() { A.a[0] };
}
и попытайтесь получить к ним доступ с помощью этого кода:
try { Console.WriteLine(B.b); } catch (Exception e) { Console.WriteLine(e.InnerException.Message.); }
try { Console.WriteLine(A.a); } catch (Exception e) { Console.WriteLine(e.InnerException.Message); }
try { Console.WriteLine(B.b); } catch (Exception e) { Console.WriteLine(e.InnerException.Message); }
мы получаем этот вывод:
The type initializer for 'A' threw an exception.
Object reference not set to an instance of an object.
The type initializer for 'A' threw an exception.
Таким образом, инициализация B
вызывает исключение в статическом конструкторе A
и левое поле A
со значением по умолчанию (null). Так как A
null
, B
также не может быть правильно инициализирован.
Если у нас нет циклических зависимостей, все работает нормально.
EDIT: на всякий случай, когда вы не читали комментарии, Jon Skeet дает очень интересное чтение: Различия между статическими конструкторами и инициализаторами типов.
Ответ 2
См. раздел 10.4 спецификации С# для правил здесь:
когда класс инициализируется, все статические поля в этом классе сначала инициализируются значениями по умолчанию, а затем инициализаторы статического поля выполняются в текстовом порядке. Аналогично, когда экземпляр класса создается, все поля экземпляра в этом экземпляре сначала инициализируются значениями по умолчанию, а затем инициализаторы полей экземпляра выполняются в текстовом порядке. Статические поля с переменными инициализаторами можно наблюдать в состоянии значения по умолчанию. Тем не менее, это сильно обескураживается как вопрос стиля.
Иными словами, в вашем примере "b" инициализируется по умолчанию (null), и поэтому ссылка на него в инициализаторе "a" является законной, но приведет к исключению NullReferenceException.
Эти правила отличаются от Java (см. раздел 8.3.2.3 JLS для правил Java о прямых ссылках, которые являются более ограничительными).
Ответ 3
Лично я бы избавился от статических инициализаторов, так как это не ясно, и добавьте статический конструктор для инициализации этих переменных.
static private List<int> a;
static private List<int> b;
static SomeClass()
{
a = new List<int>() { 0 };
b = new List<int>() { a[0] };
}
Тогда вам не нужно угадывать, что происходит, и вы четко понимаете свои намерения.
Ответ 4
Да, тебе повезло. С#, похоже, выполняет код в том порядке, в котором он появляется в классе.
static private List<int> a = new List<int>() { 0 };
static private List<int> b = new List<int>() { a[0] };
Будет работать, но...
static private List<int> b = new List<int>() { a[0] };
static private List<int> a = new List<int>() { 0 };
Ошибка.
Я бы рекомендовал разместить все ваши зависимости в одном месте, статический конструктор - это место для этого.
static MyClass()
{
a = new List<int>() { 0 };
b = new List<int>() { a[0] };
}