Порядок статических конструкторов/инициализаторов в С#

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

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] };
}