Определения типов значений в C#

Тип значения — это легковесный тип, который обычно не создается в куче. Единственное исключение из этого правила касается типа значения, которое является полем ссылочного объекта, находящегося в куче. Тип значения — это тип, поведение которого подчиняется семантике значения.

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

При передаче типа значения в качестве параметра методу тело метода получает локальную копию значения, если только параметр не был объявлен как ref или out. Все встроенные типы С# за исключением string, массивов и делегатов, являются типами значений. В С# тип значения объявляется с использованием ключевого слова struct вместо class.

В целом синтаксис определения структуры точно такой же, как у класса, но с рядом заметных исключений, в чем вы вскоре убедитесь. Для структуры нельзя объявлять базовый класс. К тому же структура является неявно герметизированной (sealed).

Это значит, что от структуры нельзя ничего унаследовать. Внутренне структура наследуется от класса System.ValueType, который, в свою очередь, расширяет System.Object.

Причина в том, что ValueType может предоставлять среди прочих реализации Object.Equals и Object.GetHashCode, что важно для типов значений.

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

Конструкторы

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

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

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

Все это — часть попыток языка по обеспечению генерации верифицируемого и безопасного в отношении типов кода. Переменную типа значения можно объявить без применения ключевого слова new; в таком случае конструктор вообще не вызывается. Тогда ответственность за соответствующую установку данных структуры перед вызовом любых ее методов возлагается на программиста.

Рассмотрим следующий код:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
using System;
public struct Square {
// Иметь общедоступные поля - плохая идея, но для примера они здесь используются. В реальном коде вместо них должны применяться свойства.
public int width;
public int height;
}
public class EntryPoint {
static void Main() {
Square sq;
sq.width = 1;
// Пока что это делать нельзя.
// Console.WriteLine( "{0} х {1}", sq.width, sq.height );
sq.height = 2;
Console.WriteLine( "{0} x {1}", sq.width, sq.height );
}

В методе Main для объекта Square выделяется пространство в стеке. Однако после этого присваивается значение только полю width. Следующий за этим вызов Console.WriteLine помещен в комментарий, поскольку в противном случае код не скомпили-руется.

Причина в том, что до полной инициализации структуры ее методы вызывать нельзя. В действительности свойства являются вызовами методов. После инициализации поля height можно успешно использовать экземпляр Square для вывода width и height на консоль.

Сможете ли вы обнаружить проблему в приведенном ниже коде?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
using System;
public struct Square {
public int Width {
get {
return width;
}
set {
width = value;
}
public int Height {
get
{
return height;
}
set {
height = value;
}
private int width;
private int height;
}
public class EntryPoint {
static void Main() {
Square sq;
sq.Width = 1;
sq.Height = 1;
}

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

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

1
2
3
4
5
6
public class EntryPoint {
static void Main() {
Square sq = new Square () ;
sq.Width = 1;
sq.Height = 1;
}

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

Вы можете следить за любыми ответами на эту запись через RSS 2.0 ленту. Вы можете оставить ответ, или trackback с вашего собственного сайта.

Оставьте отзыв

XHTML: Вы можете использовать следующие теги: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

 
Rambler's Top100