Подтвердить что ты не робот

Почему CLR не всегда вызывает конструкторы типа значения

У меня есть вопрос о конструкторах типа в типе значения. Этот вопрос был вдохновлен тем, что Джеффри Рихтер написал в CLR через С# 3rd ed, он говорит (на странице 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
    }
}

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

  • Я могу определить конструктор типа статического значения, чтобы установить начальное состояние типа.
  • Тип может иметь не более одного конструктора - по умолчанию не существует.
  • Конструкторы типов неявно закрыты
  • Компилятор JIT проверяет, был ли уже создан конструктор типа в этом AppDomain. Если он не отправляет вызов в собственный код, иначе он не знает, что тип уже инициализирован.

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

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

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

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

EDIT: Я добавил ответ на свой вопрос ниже, просто цитату из того, что Джеффри Рихтер говорит об этом.

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

4b9b3361

Ответ 1

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

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

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

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

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

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

ИЗМЕНИТЬ...

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

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

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

Ответ 2

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

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

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

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

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

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

Ответ 3

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

Обновить 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;
        }
    }

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

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

Ответ 4

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

Поскольку мы ссылаемся на стандарт здесь строка из раздела I, 8.9.5 говорит следующее:

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

Этот раздел действительно подробно излагается о том, как реализация языка может выбрать, чтобы предотвратить поведение вы описываете. С++/CLI выбирает не к, скорее они позволяют программисту сделать это, если они пожелают.

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

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

Ответ 5

Еще один интересный пример:

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

    static void Main() { new S().f(); }

Ответ 6

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

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

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

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

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

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

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

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

Ответ 7

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

Имеет смысл сказать

SomeValType i;
i._x = 5;

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

array[i] = new SomeRefType();