почему статические конструкторы не вызываются первыми, и я получаю исключение при использовании объявленной переменной?

Из MSDN

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

Ссылка MSDN

Теперь пришел к моей проблеме:

public static class DateFormat
{
    private static List<string> DateFormats = new List<string>();

    public static string DateSeparator  { get; set; } = "/";

    public static string Current { get; set; } = DateFormats[1]; // error here

    static DateFormat()
    {
        DateFormats.Add("yyyy{0}MM{0}dd HH{1}mm{1}ss");
        DateFormats.Add("yyyy{0}MM{0}dd hh{1}mm{1}ss");
    }
}

Как вы видите выше, при вызове DateFormats[1] ошибка

Инициализатор типа для DateFormat вызвал исключение.

Должен ли конструктор сначала вызывать статический конструктор? так что словарь будет заполнен, тогда любой вызов переменной, которая его использует, будет в порядке.


person Community    schedule 27.11.2019    source источник
comment
Статический конструктор используется для инициализации любых статических данных!! Сначала инициализируйте, затем заполните   -  person Eldar    schedule 27.11.2019
comment
Человек см. mSDN выше. он говорит, что статический конструктор вызывается первым.   -  person    schedule 27.11.2019
comment
Обратите внимание, что вы также можете просто инициализировать этот список и вообще не использовать конструктор.   -  person Lloyd    schedule 27.11.2019
comment
Да, он говорит, и это происходит именно так, как он говорит. Его называют первым.   -  person Eldar    schedule 27.11.2019
comment
Liyod Теперь я это знаю. но статический конструктор не вызывается первым. я сделал что-то не так?   -  person    schedule 27.11.2019


Ответы (2)


Это поведение задокументировано здесь: https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/static-constructors

В частности, в этом разделе:

Если в классе статического конструктора присутствуют инициализаторы переменных статического поля, они будут выполняться в текстовом порядке, в котором они появляются в объявлении класса непосредственно перед выполнением статического конструктора.

Поскольку инициализаторы переменных статического поля действительно присутствуют, они будут инициализированы ДО статического конструктора, поэтому вы видите эту ошибку:

public static string Current { get; set; } = DateFormats[1]; 

будет выполнен раньше

static DateFormat()
{
    DateFormats.Add("yyyy{0}MM{0}dd HH{1}mm{1}ss");
    DateFormats.Add("yyyy{0}MM{0}dd hh{1}mm{1}ss");
}

и, таким образом, конечно, список DateFormats все еще пуст в момент выполнения DateFormats[1].

Чтобы решить эту проблему, просто инициализируйте Current в статическом конструкторе:

public static class DateFormat
{
    static DateFormat()
    {
        DateFormats.Add("yyyy{0}MM{0}dd HH{1}mm{1}ss");
        DateFormats.Add("yyyy{0}MM{0}dd hh{1}mm{1}ss");

        Current = DateFormats[1];
    }

    private static List<string> DateFormats = new List<string>();

    public static string DateSeparator { get; set; } = "/";

    public static string Current { get; set; }
}
person Matthew Watson    schedule 27.11.2019
comment
@ asdk1200000: решение любой проблемы с смешиванием инициализаторов и конструкторов простое: не смешивайте их. Вместо этого напишите все в конструкторе. В фоновом режиме компилятор в любом случае должен переместить весь код инициализатора в конструктор, так что вы можете указать это явно. - person Jeroen Mostert; 27.11.2019
comment
поэтому, если есть инициализаторы, статический конструктор никогда не сработает первым. ? Это правда - person ; 27.11.2019
comment
@ asdk1200000: на уровне IL инициализаторов не существует. Компилятор берет инициализаторы и все, что вы написали в (статическом) конструкторе, и упорядочивает их в одно тело конструктора, причем инициализаторы происходят в текстовом порядке, а затем все, что вы явно помещаете в тело конструктора. Хотя все это задокументировано и предсказуемо, полагаясь на это, ваш код становится хрупким и трудным для понимания. Как только у вас есть что-то слишком сложное для обработки только для инициализаторов, вам лучше явно переместить все в конструктор, чтобы было понятно, что происходит. - person Jeroen Mostert; 27.11.2019
comment
Но, пожалуйста, когда я отлаживаю что-то, мне нужно знать... потому что это первый раз в моей жизни, когда я использую статические конструкторы. поэтому, если есть только одно статическое поле, которое инициализирует статический конструктор, он не будет вызываться первым? или поле, которое будет вызвано, если оно инициализировало статический конструктор, не вызываемый. ? - person ; 27.11.2019
comment
@asdk1200000 asdk1200000 Это происходит именно в том порядке, который описан в документации, которую я цитировал. Не уверен, как я могу уточнить это; это настолько ясно, насколько это возможно: все статические поля (если они есть) будут инициализированы перед статическим конструктором в том порядке, в котором они встречаются в файле кода. - person Matthew Watson; 27.11.2019

Вот как «переводится» весь синтаксический сахар:

static DateFormat()
{
    DateFormats = new List<string>();
    DateSeparator = "/";
    Current = DateFormats[1]; // at this point DateFormats is empty
    DateFormats.Add("yyyy{0}MM{0}dd HH{1}mm{1}ss");
    DateFormats.Add("yyyy{0}MM{0}dd hh{1}mm{1}ss");
}

Не смешивайте встроенную инициализацию и код внутри конструктора. Это головная боль. Особенно, если члены класса зависят друг от друга. Это верно и для конструкторов экземпляров.

person Dennis    schedule 27.11.2019