Почему CLR не всегда вызывает конструкторы типов значений - PullRequest
37 голосов
/ 14 июля 2010

У меня есть вопрос, касающийся конструкторов типа в типе Value . Этот вопрос был вдохновлен чем-то, что Джеффри Рихтер написал в CLR через C # 3-е издание, он говорит (на странице 195 - глава 8), что вы никогда не должны определять конструктор типа в типе значения, так как бывают ситуации, когда CLR не вызывает это.

Так, например (ну ... на самом деле ... пример Джеффри Рихтерса), я не могу понять, даже глядя на IL, почему конструктор типа не вызывается в следующем коде:

internal struct SomeValType
{
    static SomeValType()
    {
        Console.WriteLine("This never gets displayed");
    }
    public Int32 _x;
}
public sealed class Program
{
    static void Main(string[] args)
    {
        SomeValType[] a = new SomeValType[10];
        a[0]._x = 123;
        Console.WriteLine(a[0]._x);     //Displays 123
    }
}

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

  1. Я могу определить конструктор типа статического значения, чтобы установить начальное состояние типа.
  2. Тип может иметь не более одного конструктора - по умолчанию его нет.
  3. Конструкторы типов неявно закрыты
  4. JIT-компилятор проверяет, выполнялся ли конструктор типа в этом домене приложений. Если нет, то он отправляет вызов в нативный код, иначе он не делает, поскольку знает, что тип уже «инициализирован».

Итак ... Я просто не могу понять, почему я не вижу, как создается этот массив типов.

Мое лучшее предположение могло бы быть:

  1. Способ, которым CLR создает массив типов. Я бы подумал, что статический конструктор будет вызываться при создании первого элемента
  2. Код в конструкторе не инициализирует статические поля, поэтому он игнорируется. Я экспериментировал с инициализацией частных статических полей в конструкторе, но поле остается значением по умолчанию 0 - поэтому конструктор не вызывается.
  3. Или ... компилятор каким-то образом оптимизирует вызов конструктора из-за установки общедоступного Int32 - но в лучшем случае это нечеткое предположение !!

Передовая практика и т. Д. Помогите, я просто заинтригован этим, так как хочу убедиться, почему он не вызывается.

РЕДАКТИРОВАТЬ: я добавил ответ на свой вопрос ниже, просто цитата из того, что Джеффри Рихтер говорит об этом.

Если у кого-то есть идеи, это было бы замечательно. Большое спасибо, Джеймс

Ответы [ 7 ]

17 голосов
/ 14 июля 2010

Microsoft C # 4 Spec немного изменилась по сравнению с предыдущими версиями и теперь более точно отражает поведение, которое мы видим здесь:

11.3.10 Статические конструкторы

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

  • Ссылка на статический член типа struct.
  • Вызывается явно объявленный конструктор типа struct.

Создание значений по умолчанию (§11.3.4) структурных типов не вызвать статический конструктор. (An Примером этого является начальное значение элементов в массиве.)

Спецификации ECMA и Microsoft C # 3 Spec имеют дополнительное событие в этом списке: "Ссылка на экземпляр элемента типа структуры". Похоже, что C # 3 противоречит своей спецификации. Спецификация C # 4 была приведена в более тесное соответствие с фактическим поведением C # 3 и 4.

EDIT ...

После дальнейших исследований выясняется, что почти весь доступ к элементу экземпляра , за исключением прямого доступа к полю, вызовет статический конструктор (по крайней мере в текущих реализациях Microsoft C # 3 и 4).

Таким образом, текущие реализации более тесно связаны с правилами, приведенными в спецификациях ECMA и C # 3, чем в спецификации C # 4: правила C # 3 реализованы правильно при доступе ко всем элементам экземпляра , кроме полей; правила C # 4 только реализованы правильно для доступа к полю.

(Различные спецификации согласованы и, по-видимому, правильно реализованы, когда речь идет о правилах, касающихся доступа к статическим элементам и явно объявленных конструкторов.)

11 голосов
/ 14 июля 2010

Из § 18.3.10 стандарта (см. Также Язык программирования C # книга):

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

  • Экземпляр-член структуры ссылки.
  • Статический член на структуру ссылаются.
  • явно объявленный конструктор структура называется.

[ Примечание : создание значений по умолчанию (§18.3.4) структуры типы не вызывает статический конструктор. (Примером этого является начальное значение элементов в массив.) конечная нота ]

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

После тестирования все согласны с тем, что он последовательно запускает методы, свойства, события и индексаторы. Это означает, что это верно для всех явных членов экземпляра , кроме полей . Таким образом, если бы для стандарта были выбраны правила Microsoft C # 4, это привело бы к тому, что их реализация была бы в основном правильной и неправильной.

1 голос
/ 16 июля 2010

Просто обозначив это как «ответ», чтобы я мог поделиться тем, что написал о нем сам мистер Рихтер (кстати, есть ли у кого-нибудь ссылка на последнюю спецификацию CLR, легко получить версию 2006 года, но найти ееполучить последнюю версию немного сложнее):

Для такого рода вещей лучше взглянуть на спецификацию CLR, чем на C #.Спецификация CLR гласит:

4.Если не помечено BeforeFieldInit, то метод инициализатора этого типа выполняется в (т. Е. Инициируется):

• первый доступ к любому статическому полю этого типа или

• первый вызов любого статическогометод этого типа или

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

• первый вызов любого конструктора для этого типа.

Поскольку ни одно из этих условий не выполняется, статический конструктор не вызывается .Единственные хитрости, на которые стоит обратить внимание, это то, что «_x» является полем экземпляра, а не статическим полем, и построение массива структур не вызывает любые конструкторы экземпляров элементов массива.

1 голос
/ 14 июля 2010

Еще один интересный образец:

   struct S
    {
        public int x;
        static S()
        {
            Console.WriteLine("static S()");
        }
        public void f() { }
    }

    static void Main() { new S().f(); }
1 голос
/ 14 июля 2010

Это необычное поведение атрибута beforefieldinit в MSIL.Это также влияет на C ++ / CLI, я подал отчет об ошибке, в котором Microsoft очень хорошо объяснил, почему поведение такое, и я указал на несколько разделов в языковом стандарте, которые не были согласованы / должны быть обновлены, чтобы описать реальное поведение,Но это не для публичного просмотра.Во всяком случае, вот последнее слово от Microsoft (обсуждая аналогичную ситуацию в C ++ / CLI):

Так как мы здесь применяем стандарт, строка из Раздела I, 8.9.5 говорит об этом:

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

В этом разделе подробно описывается, как языкРеализация может предотвратить поведение, которое вы описываете.C ++ / CLI решает не делать этого, вместо этого они позволяют программисту делать это, если они этого желают.

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

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

1 голос
/ 14 июля 2010

Обновление: По моим наблюдениям, если не используется статическое состояние, статический конструктор никогда не будет затронут - что-то вроде того, что среда выполнения решает и не применяется к ссылочным типам.Возникает вопрос, если это оставленная ошибка, потому что она не оказывает значительного влияния, это сделано по замыслу, или это ожидающая ошибка.

Обновление 2: лично, если вы не делаете что-то напуганное вконструктор, это поведение из среды выполнения никогда не должно вызывать проблем.Как только вы получаете доступ к статическому состоянию, оно ведет себя корректно.

Update3: в дополнение к комментарию LukeH и обращению к ответу Мэтью Флэшена, реализации и вызова вашего собственного конструктора в структуре также вызываетстатический конструктор для вызова.Это означает, что в одном из трех сценариев поведение не соответствует тому, что оно говорит о жестяной коробке.

Я просто добавил статическое свойство к типу и получил доступ к этому статическому свойству - оно называется статическим конструктором.Без доступа к статическому свойству, просто создав новый экземпляр типа, статический конструктор не был вызван.

internal struct SomeValType
    {
        public static int foo = 0;
        public int bar;

        static SomeValType()
        {
            Console.WriteLine("This never gets displayed");
        }
    }

    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            // Doesn't hit static constructor
            SomeValType v = new SomeValType();
            v.bar = 1;

            // Hits static constructor
            SomeValType.foo = 3;
        }
    }

Примечание в этой ссылке указывает, что статические конструкторы не вызывается при простом доступе к экземплярам:

http://www.jaggersoft.com/pubs/StructsVsClasses.htm#default

0 голосов
/ 14 июля 2010

Я предполагаю, что вы создаете Массив вашего типа значения.Таким образом, новое ключевое слово будет использоваться для инициализации памяти для массива.

Допустимо сказать

SomeValType i;
i._x = 5;

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

array[i] = new SomeRefType();
...