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

и программа работала иначе (тестовый костюм не прошел по всем направлениям, и я не стал смотреть дальше).

Я думаю, что в файлах (т.е. в разных классах) это будет одинаково. Во время инициализации типа класса A классу B будет предложено инициализировать, и класс B найдет нулевую ссылку на класс A.

Panos 09.10.2008 04:06

Теперь, для файлов одного и того же класса (частичный класс), вероятно, препроцессор должен определить, выйдет ли он из строя или нет.

Panos 09.10.2008 04:07

Итак, если A ссылается на B.b, то получение A.a приведет к столкновению с B.b?

BCS 09.10.2008 04:09
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
26
3
9 808
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 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 со значением по умолчанию (null). Поскольку a - это null, b также не может быть правильно инициализирован.

Если у нас нет циклических зависимостей, все работает нормально.


Обновлено: На всякий случай, если вы не читали комментарии, Джон Скит дает очень интересное чтение: Различия между статическими конструкторами и инициализаторами типов.

Это точно. Статический конструктор вызывается при первой ссылке на тип.

Panos 09.10.2008 04:53

Будьте осторожны с различиями между инициализаторами статических переменных и статическими конструкторами. Существуют разные правила, когда инициализация типа происходит на основе наличия / отсутствия статического конструктора. См. pobox.com/~skeet/csharp/beforefieldinit.html

Jon Skeet 09.10.2008 06:06

Да, тебе повезло. Кажется, что 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] };
}

Лично я бы избавился от статических инициализаторов, поскольку они не ясны, и добавил бы статический конструктор для инициализации этих переменных.

static private List<int> a;
static private List<int> b;

static SomeClass()
{
    a = new List<int>() { 0 };
    b = new List<int>() { a[0] };
}

Тогда вам не нужно гадать, что происходит, и вы ясно понимаете свои намерения.

Обратите внимание, что эти фрагменты кода не эквивалентны точно с точки зрения времени их выполнения: pobox.com/~skeet/csharp/beforefieldinit.html

Jon Skeet 09.10.2008 06:05

См. Правила здесь раздел 10.4 спецификации C#:

when a class is initialized, all static fields in that class are first initialized to their default values, and then the static field initializers are executed in textual order. Likewise, when an instance of a class is created, all instance fields in that instance are first initialized to their default values, and then the instance field initializers are executed in textual order. It is possible for static fields with variable initializers to be observed in their default value state. However, this is strongly discouraged as a matter of style.

Другими словами, в вашем примере «b» инициализируется своим состоянием по умолчанию (null), и поэтому ссылка на него в инициализаторе «a» является допустимой, но приведет к исключению NullReferenceException.

Эти правила отличаются от правил Java (см. раздел 8.3.2.3 JLS для правил Java о прямых ссылках, которые являются более строгими).

Отличный ответ to the question I asked. However it seems I didn't ask the question I wanted to: what about across files?
BCS 09.10.2008 04:02

Я не специалист по C# (просто знал, где искать), но обратите внимание на msdn.microsoft.com/en-us/library/aa645612(VS.71).aspx - «Можно построить циклические зависимости, которые позволяют наблюдать статические поля с инициализаторами переменных в их состоянии значения по умолчанию». Это помогает?

Cowan 09.10.2008 04:06

@ Кован, не совсем. Он не решает проблему перекрестных файлов.

BCS 09.10.2008 04:12

Другие вопросы по теме