Статическое поле инициализируется позже, когда класс имеет статический конструктор.

Запустив этот простой код:

class Program
{
    class MyClassWithStatic
    {
        public static int Number = SomeService.GetData();

        static MyClassWithStatic()
        {
            Console.WriteLine("static ctor runs");
        }
    }

    class SomeService
    {
        public static int GetData()
        {
            Console.WriteLine("GetDataRuns");
            return 42;
        }
    }        

    static void Main(string[] args)
    {
        InitService();

        var value = MyClassWithStatic.Number;
        Console.WriteLine(value);
    }

    private static void InitService()
    {
        Console.WriteLine("InitServiceRuns");
    }
}

Вывод на моей машине такой:

InitServiceRuns
GetDataRuns
выполняется статический ctor
42

Это означает, что сначала вызывается метод InitService, затем инициализируется статическое поле MyClassWithStatic, а затем вызывается статический конструктор (фактически, глядя на это в ILSpy и IlDasm, мы можем видеть, что инициализация статических полей происходит в начале кктор)

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

class MyClassWithStatic
{
    public static int Number = SomeService.GetData();
}

Вывод таков:

GetDataRuns
InitServiceRuns
42

Это означает, что при удалении статического конструктора статические поля инициализируются раньше. Поскольку инициализация является частью статического конструктора (я говорю это, изучая его с помощью ildasm), эффект в основном заключается в том, что статический конструктор вызывается раньше.

Итак, вот вопрос:

  1. Может кто-нибудь объяснить такое поведение? Что может быть причиной этого?

  2. Есть ли что-то еще, что может измениться при вызове статического конструктора? (Например, присоединение профилировщика или запуск его в IIS и т. д.) (Я сравнил отладку, режим выпуска, x86, x64, и все они показывают одинаковое поведение)

Некоторые общие вещи:

-Это было в консольном приложении .NET 4.6. Я также перешел на .NET 2 (должен работать с другим clr, и поведение такое же, это не имеет никакого значения)

- Я также пробовал это с ядром .NET: как с cctor, так и без него сначала вызывается метод InitService.

-Теперь мне абсолютно известна эта страница:

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

И я также знаю, что в статическом конструкторе есть много вещей, которые вы не должны делать. Но, к сожалению, мне приходится иметь дело с кодом, в котором эта часть находится вне моего контроля, и разница, которую я описал, имеет огромное значение. (И я также рассмотрел много вопросов SO, связанных с C# cctor..)

(И Вопрос №3:) Так не является ли все, что я описал, немного проблематичным?


person gregkalapos    schedule 14.11.2015    source источник
comment
Похоже на проблему XY. Можете ли вы описать, почему вы хотите иметь контроль над этим?   -  person Hamid Pourjam    schedule 14.11.2015
comment
Проблема хорошо объяснена в первых ответах, но в каком направлении вам нужен ответ или обходной путь? Каково желаемое поведение и какие классы вы можете изменить?   -  person Henk Holterman    schedule 14.11.2015
comment
На самом деле ситуация такова, что в классе есть только статические поля (= мой второй пример) и когда к нему прикреплен профилировщик, то поля инициализируются раньше. Теперь я не знал о флаге beforefieldinit, поэтому я экспериментировал с добавлением/удалением cctor. Я не хочу ничего менять... Я просто хочу понять, почему. Итак, что мы можем сказать, так это то, что если тип помечен с помощью beforefieldinit, то подключение профилировщика, похоже, имеет значение (что абсолютно синхронизировано с документацией...)   -  person gregkalapos    schedule 14.11.2015


Ответы (2)


Может кто-нибудь объяснить такое поведение? Что может быть причиной этого?

У @JonSkeet есть абзац в C# in Depth о статических полях и статических конструкторах. Вот фрагмент:

Спецификация С# гласит:

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

    • An instance of the class is created.
    • Ссылаются на любой из статических членов класса.

Спецификация CLI (ECMA 335) гласит в разделе 8.9.5:

Тип может иметь метод инициализации типа или нет. Тип может быть указан как имеющий расслабленную семантику для своего метода инициализации типа (ниже для удобства мы называем эту упрощенную семантику BeforeFieldInit):.

  • Если отмечено BeforeFieldInit, то метод инициализации типа выполняется при первом доступе к любому статическому полю, определенному для этого типа, или когда-то раньше.
  • Если не помечен BeforeFieldInit, то метод инициализации этого типа выполняется ((в (т. е. инициируется): первым доступом к любому статическому полю или полю экземпляра этого типа или первым вызовом любого статического, экземплярного или виртуального метода этого типа))

Это показывает вам, что когда тип не имеет флага beforefieldinit, среда выполнения может вызвать его в произвольное время, учитывая, что это происходит до первого доступа к любому определенному статическому полю, что именно то, что вы видеть.

Есть ли что-то еще, что может измениться при вызове статического конструктора?

Единственное, что нужно сделать, это создать конструктор статического типа для вашего типа. В противном случае вы не можете контролировать его вызов.

Так не является ли все, что я описал, немного проблематичным?

Проблемный в каком плане? Я не вижу проблем, пока ты знаешь, за что идешь. Спецификация CLI абсолютно ясно дает понять, какие гарантии у вас есть с инициализатором типа и без него. Таким образом, если вы будете следовать этим рекомендациям, не должно быть никакой двусмысленности.

person Yuval Itzchakov    schedule 14.11.2015
comment
Большое спасибо! Как вы видите мой комментарий выше, в моем случае класс помечен beforefieldinit... Знаете ли вы, в этом случае, если я прикреплю профилировщик, инициализация произойдет раньше? (поскольку это все еще будет синхронизировано с документацией). Итак, в основном, после понимания этого случая, мой второй вопрос меняется на это: есть ли что-нибудь, что может измениться, когда происходит инициализация типа в случае класса, отмеченного beforefieldinit (например, работающего в IIS, подключенного профилировщика и т. д.)? - person gregkalapos; 14.11.2015
comment
@gregkalapos Нет. Время выполнения должно решить, когда его инициализировать, насколько я знаю. - person Yuval Itzchakov; 14.11.2015

Класс со статическим конструктором не будет помечен флагом beforefieldinit, что позволяет среде выполнения инициализировать его позже (другими словами, MyClassWithStatic.Number будет инициализирован при первой ссылке/доступе к MyClassWithStatic)

Прочтите эту статью, чтобы получить дополнительную информацию.

person Matias Cicero    schedule 14.11.2015