Порядок статических конструкторов / инициализаторов в 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] };
}
Другие вопросы по тегам