Сдам Сам

ПОЛЕЗНОЕ


КАТЕГОРИИ







Конструкторы экземпляров и структуры (значимые типы)





Конструкторы значимых типов {struct) работают иначе, чем конструкторы ссылочных типов {class). CLR всегда разрешает создание экземпляров значимых типов, и этому ничто не может помешать. Поэтому, по большому счету, конструкторы у значимого типа можно не определять. Фактически многие компиляторы (включая С#) не определяют для значимых типов конструкторы по умолчанию, не имеющие параметров. Разберем такой код:

internal struct Point

{

public Int32 m_x, m_y;

}

internal sealed class Rectangle

{

public Point m_topLeft, m_bottomRight;

}

 

Чтобы создать объект Rectangle, надо использовать оператор new, указав конструктор. В этом случае вызывается конструктор, автоматически сгенерированный компилятором С#. Память, выделенная для объекта Rectangle, включает место для двух экземпляров значимого типа Point. Из соображений повышения производительности CLR не пытается вызвать конструктор для каждого экземпляра значимого типа, содержащегося внутри объекта ссылочного типа. Но, как сказано выше, поля значимого типа инициализируются нулевыми или пустыми значениями.

CLR действительно позволяет программистам определять конструкторы для значимых типов, но эти конструкторы будут выполнены лишь при наличии кода, явно вызывающего один из них, например, как в конструкторе объекта Rectangle:

internal struct Point

{

public Int32 m_x, m_y;

public Point(Int32 x, Int32 y)

{

m_x = x;

m_y = y;

}

}

internal sealed class Rectangle

{

public Point m_topLeft, m_bottomRight;

public Rectangle()

{

/* В С# оператор new, использованный для создания экземпляра

значимого типа, вызывает конструктор для инициализации

полей значимого типа*/

m_topleft = new Point(1, 2);

m_bottomRight = new Point(100, 200);

}

}

 

Конструктор экземпляра значимого типа будет исполнен, только если вызвать его явно. Так что, если конструктор объекта Rectangle не инициализировал его поля mtopLeft и mbottomRight вызовом конструктора Point оператором new, поля m_х и m_у у обеих структур Point будут содержать 0.

Если значимый тип Point уже определен, то конструктор по умолчанию, не имеющий параметров, не определяется. Но давайте перепишем наш код:

internal struct Point

{

public Int32 m_x, m_y;

public Point()

{

m_x = m_y = 5;

}

}

internal sealed class Rectangle

{

public Point m_topLeft, m_bottomRight;

public Rectangle() { }

}

 

А теперь скажите: какими значениями — 0 или 5 — будут инициализированы поля m_х и m_y, принадлежащие структурам Point (m_topLeft и m_bottomRight)? (Предупреждаю: вопрос с подвохом.)

Многие разработчики (особенно с опытом программирования на C++) будут ожидать, что компилятор С# поместит в конструктор Rectangle код, автоматически вызывающий конструктор структуры Point по умолчанию, не имеющий параметров, для двух полей Rectangle. Но, чтобы увеличить быстродействие приложения во время выполнения, компилятор С# не сгенерирует такой код автоматически. Фактически большинство компиляторов никогда не генерирует автоматически код для вызова конструктора по умолчанию для значимого типа, даже если у него есть конструктор без параметров. Чтобы принудительно исполнить конструктор значимого типа, не имеющий параметров, разработчик должен добавить код для явного вызова конструктора значимого типа.

С учетом сказанного можно ожидать, что поля m_x и m_y обеих структур Point из объекта Rectangle в показанном выше коде будут инициализированы нулевыми значениями, так как в этой программе нет явного вызова конструктора Point.

Однако, как я сказал, мой первый вопрос был с подвохом. Подвох в том, что С# не позволяет определять для значимого типа конструкторы без параметров. Поэтому показанный выше код на самом деле даже не компилируется. При попытке скомпилировать его компилятор С# генерирует сообщение об ошибке: «error CSO568: Structs cannot contain explicit parameterless constructors» («ошибка CSO568: структура не может содержать явные конструкторы без параметров»).

Примечание. Строго говоря, в поля значимого типа обязательно заносятся значения 0 или null, если значимый тип является вложенным в объект ссылочного типа. Однако где гарантия, что поля значимых типов, работающих со стеком, будут инициализированы значениями 0 или null! Чтобы код был верифицируемым, перед чтением любого поля значимого типа, работающего со стеком, нужно записать в него значение. Если код сможет прочитать значение поля значимого типа до того, как туда будет записано какое-то значение, может нарушиться безопасность. С# и другие компиляторы, генерирующие верифицируемый код, гарантируют, что поля любых значимых типов, работающие со стеком, перед чтением обнуляются или в них хотя бы записываются некоторые значения. Поэтому при верификации во время выполнения исключение сгенерировано не будет. Но обычно можно предполагать, что поля значимых типов инициализируются нулевыми значениями, и все сказанное в этом примечании можно полностью игнорировать.

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

 

Хотя С# не допускает использования значимых типов с конструкторами без параметров, это допускает CLR. Так что, если вас не беспокоят скрытые особенности работы системы, описанные выше, можно на другом языке (например, на IL) определить собственный значимый тип с конструктором без параметров.

Поскольку С# не допускает использования значимых типов с конструкторами без параметров, при компиляции следующего типа компилятор сообщает об ошибке: «error CSO573: 'SomeValType.m_x': cannot have instance field initializers in structs» (нельзя создавать конструкторы экземплярных полей в структурах).

internal struct SomeValType

{

// В значимом типе нельзя встраивать инициализацию экземплярных полей

private Int32 m_x = 5;

}

 

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

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

internal struct SomeValType

{

private Int32 m_x, m_y;

// C# допускает наличие у значимых типов конструкторов с параметрами,

public SomeValType(Int32 x)

{

m_х = х;

// Обратите внимание: поле m_у здесь не инициализируется

}

}

 

При компиляции этого типа компилятор С# генерирует сообщение об ошибке: «error CSO171: Field 'SomeVallype.m_y' must be fully assigned before control leaves the constructor» (поле 'SomeValType.m_y' должно быть полностью определено до возвращения управления конструктором). Чтобы разрешить эту проблему, в поле m_y надо занести значение (обычно это 0) в конструкторе.

Конструкторы типов

Помимо конструкторов экземпляров, CLR поддерживает конструкторы типов (также известные как статические конструкторы, конструкторы классов или инициализаторы типов). Конструкторы типа можно применять и к интерфейсам (хотя С# этого не допускает), ссылочным и значимым типам. Подобно тому, как конструкторы экземпляров используются для установки первоначального состояния экземпляра типа, конструкторы типов применяются для установки первоначального состояния типа. По умолчанию у типа не определен ни один конструктор. У типа может быть один и только один конструктор. Кроме того, у конструкторов типа никогда не бывает параметров. Вот как определяются ссылочные и значимые типы с конструкторами в программах на С#:

internal sealed class SomeRefType

{

static SomeRefType()

{

// Исполняется при первом обращении к ссылочному типу SomeRefType

}

}

internal struct SomeValType

{

/* С# на самом деле допускает определять для значимых типов

конструкторы, не имеющие параметров*/

static SomeValType()

{

// Исполняется при первом обращении к значимому типу SomeValType

}

}

 

Заметьте: конструкторы типа определяют так же, как конструкторы экземпляров, не имеющие параметров, за исключением того, что их помечают как статические. Кроме того, конструкторы типа всегда должны быть закрытыми (С# делает их закрытыми автоматически). Но, если явно пометить в исходном тексте программы конструктор типа как закрытый (или как-то иначе), компилятор С# выведет сообщение об ошибке. Конструкторы типа всегда должны быть закрытыми, чтобы код разработчика не смог их вызвать, — напротив, CLR всегда способна вызвать конструктор типа.

 

Внимание! Хотя конструктор типа можно определить в значимом типе, этого никогда не следует делать, так как иногда CLR не вызывает статический конструктор значимого типа. Вот пример:

internal struct SomeValType

{

static SomeValType()

{

Console.WriteLine("This never gets displayed");

}

public Int32 m_x;

}

 

public sealed class Program

{

public static void Main()

{

SomeValType[] a = new SomeValType[10];

a[0].m_x = 123;

Console.WriteLine(a[O].m_x); // Отображаем 123

}

}

 

Есть определенные особенности вызова конструктора типа. При компиляции метода JIT-компилятор обнаруживает типы, на которые есть ссылки из кода. Если в каком-либо из типов определен конструктор, JIT-компилятор проверяет, был ли исполнен конструктор типа в данном домене AppDomain. Если нет, JIT-компилятор создает в IL-коде вызов конструктора типа. Если же код уже исполнялся, JIT-компилятор вызова конструктора типа не создает, так как «знает», что тип уже инициализирован. (Пример подобного поведения см. в разделе «Производительность конструкторов типа».)

Примечание. Поскольку CLR гарантирует, что конструктор типа выполняется только однажды в каждом домене AppDomain, а также обеспечивает его безопасность по отношению к потокам, конструктор типа лучше всего подходит для инициализации всех Singleton-объектов, необходимых для существования типа.

Далее, после JIT-компиляции метода, начинается выполнение потока, и в конечном итоге очередь доходит до выполнения кода вызова конструктора. В реальности, может оказаться, что несколько потоков одновременно начнут выполнять метод. CLR стремится обеспечить, чтобы конструктор типа выполнялся только раз в каждом домене AppDomain. Для этого при вызове конструктора типа вызывающий поток получает исключающую блокировку синхронизации потоков. Поэтому, если несколько потоков одновременно попытаются вызывать конструктор типа, только один получит блокировку, а остальные блокируются. Первый поток выполнит код статического конструктора. После выхода из конструктора первого потока, «проснутся» простаивающие потоки и проверят, был ли выполнен конструктор. Они не станут снова выполнять код, а просто выполнят возврат управления из метода конструктора. Кроме того, при последующем вызове какого-либо из этих методов CLR будет «в курсе», что конструктор типа уже выполнялся, и предотвратит еще одно его выполнение.

Наконец, если конструктор типа генерирует необрабатываемое исключение, CLR считает такой тип непригодным. При попытке обращения к любому полю или методу такого типа возникает исключение System.TypelnitializationException.

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

internal sealed class SomeType

{

private static Int32 s_x = 5;

}

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

 

При компоновке этого кода компилятор автоматически генерирует конструктор типа SomeType. Иначе говоря, получается тот же эффект, как если бы этот код был исходно написан так:

internal sealed class SomeType

{

private static Int32 s_x;

static SomeType() { s_x = 5; }

}

 

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

 

Примечание. Ряд языков, таких как Java, ожидает, что при обращении к типу будет вызван его конструктор, а также конструкторы всех его базовых типов. Кроме того, интерфейсы, реализованные этими типами, тоже должны вызывать свои конструкторы. CLR не поддерживает такую семантику, но позволяет компиляторам и разработчикам предоставлять поддержку подобной семантики через метод RunClassConstructor, поддерживаемыйтипом SystemRuntime.CompilerServicesRuntimeHelpers. Компилятор любого языка, требующего подобную семантику, генерирует в конструкторе типа код, вызывающий этот метод для всех базовых типов. При использовании метода RunClassConstructor для вызова конструктора типа CLR определяет, был ли он исполнен ранее, и если да, то не вызывает его снова.

 

В завершение этого раздела рассмотрим код:

internal sealed class SomeType

{

private static Int32 s_x = 5;

static SomeType()

{ s_x = 10;}

}

 

Здесь компилятор С# генерирует единственный метод-конструктор типа, который сначала инициализирует поле s_x значением 5, затем — значением 10. Иначе говоря, при генерации IL-кода конструктора типа компилятор С# сначала генерирует код, инициализирующий статические поля, затем обрабатывает явный код, содержащийся внутри метода-конструктора типа.

 







ЧТО ПРОИСХОДИТ ВО ВЗРОСЛОЙ ЖИЗНИ? Если вы все еще «неправильно» связаны с матерью, вы избегаете отделения и независимого взрослого существования...

ЧТО И КАК ПИСАЛИ О МОДЕ В ЖУРНАЛАХ НАЧАЛА XX ВЕКА Первый номер журнала «Аполлон» за 1909 г. начинался, по сути, с программного заявления редакции журнала...

Что способствует осуществлению желаний? Стопроцентная, непоколебимая уверенность в своем...

Что будет с Землей, если ось ее сместится на 6666 км? Что будет с Землей? - задался я вопросом...





Не нашли то, что искали? Воспользуйтесь поиском гугл на сайте:


©2015- 2024 zdamsam.ru Размещенные материалы защищены законодательством РФ.