Порядок статических конструкторов / инициализаторов в C#
Работая над приложением C#, я только что заметил, что в нескольких местах статические инициализаторы имеют зависимости друг от друга, например:
static private List<int> a = new List<int>() { 0 };
static private List<int> b = new List<int>() { a[0] };
Не делая ничего особенного, что работало. Это просто удача? Есть ли в C# правила для решения этой проблемы?
Редактировать: (re: Panos) В файле лексический порядок кажется королем? как насчет файлов?
В поиске я попробовал циклическую зависимость, например, такую:
static private List<int> a = new List<int>() { b[0] };
static private List<int> b = new List<int>() { a[0] };
и программа не работала одинаково (тестовый костюм не прошел через доску, и я не смотрел дальше).
4 ответа
Кажется, это зависит от последовательности строк. Этот код работает:
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
со значением по умолчанию (ноль). поскольку a
является null
, b
также не может быть правильно инициализирован.
Если у нас нет циклических зависимостей, все работает нормально.
РЕДАКТИРОВАТЬ: На тот случай, если вы не читали комментарии, Jon Skeet предлагает очень интересное чтение: различия между статическими конструкторами и инициализаторами типов.
См. Раздел 10.4 спецификации C# для правил здесь:
когда класс инициализируется, все статические поля в этом классе сначала инициализируются в их значения по умолчанию, а затем инициализаторы статических полей выполняются в текстовом порядке. Аналогично, когда создается экземпляр класса, все поля экземпляра в этом экземпляре сначала инициализируются в их значения по умолчанию, а затем инициализаторы поля экземпляра выполняются в текстовом порядке. Статические поля с инициализаторами переменных могут наблюдаться в состоянии по умолчанию. Тем не менее, это настоятельно не рекомендуется как вопрос стиля.
Другими словами, в вашем примере "b" инициализируется в состояние по умолчанию (ноль), и поэтому ссылка на него в инициализаторе "a" является допустимой, но приведет к исключению NullReferenceException.
Эти правила отличаются от Java (см. Раздел 8.3.2.3 JLS для правил Java о прямых ссылках, которые являются более строгими).
Лично я бы избавился от статических инициализаторов, так как это не ясно, и добавил бы статический конструктор для инициализации этих переменных.
static private List<int> a;
static private List<int> b;
static SomeClass()
{
a = new List<int>() { 0 };
b = new List<int>() { a[0] };
}
Тогда вам не нужно угадывать, что происходит, и вы четко понимаете свои намерения.
Да, тебе повезло. C# появляется для выполнения кода в том порядке, в котором он появляется в классе.
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] };
}