16 структур
16.1 Общие
Структуры похожи на классы, представляющие структуры данных, которые могут содержать элементы данных и члены функции. Однако, в отличие от классов, структуры являются типами значений и не требуют выделения кучи. Переменная типа напрямую содержит данные, а переменная struct
типа класса содержит ссылку на данные struct
, последняя называется объектом.
Примечание. Структуры особенно полезны для небольших структур данных, имеющих семантику значений. Хорошими примерами структур можно считать комплексные числа, точки в системе координат или словари с парами ключ-значение. Ключом к этим структурам данных является то, что они имеют несколько элементов данных, которые не требуют использования семантики наследования или ссылки, а могут быть удобно реализованы семантикой значений, где назначение копирует значение вместо ссылки. конечная заметка
Как описано в §8.3.5, простые типы, предоставляемые C#, такие как int
, double
и bool
, на самом деле, являются всеми типами структур.
Объявления структуры 16.2
16.2.1 Общие
Struct_declaration — это type_declaration (§14.7), которая объявляет новую структуру:
struct_declaration
: attributes? struct_modifier* 'ref'? 'partial'? 'struct'
identifier type_parameter_list? struct_interfaces?
type_parameter_constraints_clause* struct_body ';'?
;
Struct_declaration состоит из необязательного набора атрибутов (§22), за которым следует необязательный набор struct_modifiers (§16.2.2), а затем необязательный модификатор (§16.2.3), за которым следует необязательный ref
частичный модификатор (§15.2.7), а затем ключевое слово struct
и идентификатор, который именует структуру, за которым следует дополнительная спецификация type_parameter_list (§15.2.3За которым следует необязательная спецификация struct_interfaces (§16.2.5), за которой следует необязательное спецификация предложения type_parameter_constraints (§15.2.5), за которой следует struct_body (§16.2.6), за которым следует точка с запятой.
Объявление структуры не должно предоставлять type_parameter_constraints_clauses , если он также не предоставляет type_parameter_list.
Объявление структуры, которое предоставляет type_parameter_list , является универсальным объявлением структуры. Кроме того, любая структура, вложенная в объявление универсального класса или универсальное объявление структуры, является универсальным объявлением структуры, так как аргументы типов для содержащего типа должны быть предоставлены для создания созданного типа (§8.4).
Объявление структуры, включающее ref
ключевое слово, не должно содержать часть struct_interfaces .
Модификаторы структуры 16.2.2
При необходимости struct_declaration может содержать последовательность struct_modifier:
struct_modifier
: 'new'
| 'public'
| 'protected'
| 'internal'
| 'private'
| 'readonly'
| unsafe_modifier // unsafe code support
;
unsafe_modifier (§23.2) доступен только в небезопасном коде (§23).
Это ошибка во время компиляции для одного модификатора, который будет отображаться несколько раз в объявлении структуры.
Кроме того readonly
, модификаторы объявления структуры имеют то же значение, что и объявление класса (§15.2.2).
Модификатор readonly
указывает, что struct_declaration объявляет тип, экземпляры которого неизменяемы.
У структуры чтения есть следующие ограничения:
- Каждый из полей экземпляра также должен быть объявлен
readonly
. - Ни один из его свойств экземпляра не должен иметь set_accessor_declaration (§15.7.3).
- Оно не должно объявлять какие-либо события, подобные полю (§15.8.2).
Когда экземпляр структуры чтения передается методу, он this
обрабатывается как входной аргумент/параметр, который запрещает доступ на запись к любым полям экземпляра (за исключением конструкторов).
Модификатор ref 16.2.3
Модификатор ref
указывает, что struct_declaration объявляет тип, экземпляры которого выделяются в стеке выполнения. Эти типы называются типами структур ссылок . Модификатор ref
объявляет, что экземпляры могут содержать поля ссылок и не должны быть скопированы из его безопасного контекста (§16.4.12). Правила определения безопасного контекста структуры ссылок описаны в разделе 16.4.12.
Это ошибка во время компиляции, если тип структуры ref используется в любом из следующих контекстов:
- В качестве типа элемента массива.
- Как объявленный тип поля класса или структуры, у которых нет
ref
модификатора. - Быть в коробке или
System.ValueType
System.Object
. - В качестве аргумента типа.
- Тип элемента кортежа.
- Асинхронный метод.
- Итератор.
- Преобразование типа в
ref struct
типobject
или типSystem.ValueType
отсутствует. ref struct
Тип не должен быть объявлен для реализации любого интерфейса.- Метод экземпляра, объявленный в
object
илиSystem.ValueType
в, но не переопределен в типеref struct
, не должен вызываться приемником этогоref struct
типа. - Метод экземпляра
ref struct
типа не должен быть записан преобразованием группы методов в тип делегата. - Структура ссылок не должна быть захвачена лямбда-выражением или локальной функцией.
Примечание. Не
ref struct
следует объявлятьasync
методы экземпляра иyield break
не использоватьyield return
инструкцию в методе экземпляра, так как неявныйthis
параметр нельзя использовать в этих контекстах. конечная заметка
Эти ограничения гарантируют, что переменная ref struct
типа не ссылается на память стека, которая больше не допустима, или к переменным, которые больше не являются допустимыми.
16.2.4 Частичный модификатор
Модификатор partial
указывает, что этот struct_declaration является объявлением частичного типа. Несколько частичных объявлений структур с одинаковым именем в заключающее пространство имен или объявление типа объединяются для формирования одного объявления структуры, следуя правилам, указанным в §15.2.7.
Интерфейсы структуры 16.2.5
Объявление структуры может включать спецификацию struct_interfaces , в этом случае структура, как сообщается, напрямую реализует указанные типы интерфейса. Для созданного типа структуры, включая вложенный тип, объявленный в объявлении универсального типа (§15.3.9.7), каждый реализованный тип интерфейса получается путем подстановки для каждого type_parameter в данном интерфейсе, соответствующего type_argument созданного типа.
struct_interfaces
: ':' interface_type_list
;
Обработка интерфейсов в нескольких частях объявления частичной структуры (§15.2.7) рассматривается далее в §15.2.4.3.
Реализации интерфейса рассматриваются далее в §18.6.
Текст структуры 16.2.6
Struct_body структуры определяет элементы структуры.
struct_body
: '{' struct_member_declaration* '}'
;
Элементы структуры 16.3
Члены структуры состоят из элементов, представленных его struct_member_declarationи членами, унаследованными от типа System.ValueType
.
struct_member_declaration
: constant_declaration
| field_declaration
| method_declaration
| property_declaration
| event_declaration
| indexer_declaration
| operator_declaration
| constructor_declaration
| static_constructor_declaration
| type_declaration
| fixed_size_buffer_declaration // unsafe code support
;
fixed_size_buffer_declaration (§23.8.2) доступен только в небезопасном коде (§23).
Примечание. Все виды class_member_declaration, кроме finalizer_declaration, также struct_member_declaration. конечная заметка
За исключением различий, указанных в §16.4, описания членов класса, предоставленных в §15.3 до §15.12, применяются к элементам структуры, а также.
Различия классов и структур 16.4
16.4.1 Общие
Структуры отличаются от классов несколькими важными способами:
- Структуры — это типы значений (§16.4.2).
- Все типы структур неявно наследуются от класса
System.ValueType
(§16.4.3). - Назначение переменной типа структуры создает копию присваиваемого значения (§16.4.4).
- Значение структуры по умолчанию — это значение, созданное путем установки всех полей в значение по умолчанию (§16.4.5).
- Операции бокса и распаковки используются для преобразования между типом структуры и определенными ссылочными типами (§16.4.6).
- Смысл отличается в членах
this
структуры (§16.4.7). - Объявления полей экземпляра для структуры не допускаются включать инициализаторы переменных (§16.4.8).
- Не разрешено объявлять конструктор экземпляра без параметров (§16.4.9).
- Структуру не разрешено объявлять метод завершения.
Семантика значения 16.4.2
Структуры — это типы значений (§8.3) и имеют семантику значений. Классы, с другой стороны, являются ссылочными типами (§8.2) и, как говорят, имеют ссылочные семантики.
Переменная типа структуры напрямую содержит данные структуры, в то время как переменная типа класса содержит ссылку на объект, содержащий данные. Если структура B
содержит поле экземпляра типа и A
является типом A
структуры, это ошибка во время компиляции для A
зависимости B
или типа, созданного на основеB
. A struct X
напрямую зависит от структурыY
, если X
содержит поле типа Y
экземпляра. Учитывая это определение, полный набор структур, от которых зависит структура, является транзитивным закрытием непосредственно зависит от связи.
Пример:
struct Node { int data; Node next; // error, Node directly depends on itself }
является ошибкой, так как
Node
содержит поле экземпляра собственного типа. Другой примерstruct A { B b; } struct B { C c; } struct C { A a; }
— это ошибка, так как каждый из типов
A
иB
C
зависит друг от друга.пример конца
Классы позволяют двум переменным ссылаться на один и тот же объект и, следовательно, могут влиять на объект, на который ссылается другая переменная. При использовании структур переменные имеют собственную копию данных (за исключением случаев параметров по ссылке), и для операций с одной из них нельзя повлиять на другую. Кроме того, за исключением случаев, когда явно допускается значение NULL (§8.3.12), нельзя использовать значения типа null
структуры.
Примечание. Если структуру содержит поле ссылочного типа, содержимое объекта, на который ссылается, может быть изменено другими операциями. Однако значение самого поля, т. е. объекта, на который он ссылается, невозможно изменить путем изменения другого значения структуры. конечная заметка
Пример. Учитывая следующее:
struct Point { public int x, y; public Point(int x, int y) { this.x = x; this.y = y; } } class A { static void Main() { Point a = new Point(10, 10); Point b = a; a.x = 100; Console.WriteLine(b.x); } }
выходные данные .
10
Назначениеa
создаетb
копию значения иb
, таким образом, не влияет на назначениеa.x
. ВместоPoint
этого был объявлен как класс, выходные данные будут вызваны100
a
тем же объектом иb
ссылаться на тот же объект.пример конца
Наследование 16.4.3
Все типы структур неявно наследуются от класса System.ValueType
, который, в свою очередь, наследует от класса object
. Объявление структуры может указывать список реализованных интерфейсов, но объявление структуры невозможно указать базовый класс.
Типы структур никогда не являются абстрактными и всегда неявно запечатаны. Поэтому abstract
модификаторы и sealed
модификаторы не допускаются в объявлении структуры.
Так как наследование не поддерживается для структур, объявленная доступность элемента структуры не может быть protected
, private protected
или protected internal
.
Члены функции в структуре не могут быть абстрактными или виртуальными, а override
модификатор разрешен только переопределить методы, унаследованные от System.ValueType
.
Назначение 16.4.4
Назначение переменной типа структуры создает копию присваиваемого значения. Это отличается от назначения переменной типа класса, которая копирует ссылку, но не объект, определенный ссылкой.
Аналогично назначению, когда структура передается в качестве параметра значения или возвращается в результате члена функции, создается копия структуры. Структуру можно передать по ссылке на член функции с помощью параметра по ссылке.
Если свойство или индексатор структуры является целью назначения, выражение экземпляра, связанное со свойством или доступом индексатора, должно классифицироваться как переменная. Если выражение экземпляра классифицируется как значение, возникает ошибка во время компиляции. Это подробно описано в разделе 12.21.2.
Значения по умолчанию 16.4.5
Как описано в разделе 9.3, при создании переменных несколько типов переменных автоматически инициализированы в их значение по умолчанию. Для переменных типов классов и других ссылочных типов это значение null
по умолчанию. Однако, так как структуры являются типами значений, которые не могут бытьnull
, значение по умолчанию структуры является значением, созданным путем установки всех полей типа значения по умолчанию и всех полей ссылочного типа.null
Пример: ссылка на структуру
Point
, объявленную выше, примерPoint[] a = new Point[100];
инициализирует
y
каждыйPoint
из массивов значением, созданным путем заданияx
полей нулю.пример конца
Значение по умолчанию структуры соответствует значению, возвращаемого конструктором структуры по умолчанию (§8.3.3). В отличие от класса, структуру нельзя объявить конструктор экземпляра без параметров. Вместо этого каждая структура неявно имеет конструктор экземпляра без параметров, который всегда возвращает значение, которое приводит к настройке всех полей в значения по умолчанию.
Примечание. Структуры должны быть разработаны для рассмотрения состояния инициализации по умолчанию допустимого состояния. В примере
struct KeyValuePair { string key; string value; public KeyValuePair(string key, string value) { if (key == null || value == null) { throw new ArgumentException(); } this.key = key; this.value = value; } }
Определяемый пользователем конструктор экземпляра защищает
null
только от значений, где он вызывается явным образом. В случаях, когдаKeyValuePair
переменная подвергается инициализации значений по умолчанию,key
value
поля иnull
поля должны быть подготовлены для обработки этого состояния.конечная заметка
16.4.6 Бокс и распаковка
Значение типа класса можно преобразовать в тип object
или в тип интерфейса, реализуемый классом, просто рассматривая ссылку как другой тип во время компиляции. Аналогичным образом, значение типа object
или значения типа интерфейса можно преобразовать обратно в тип класса, не изменив ссылку (но, конечно, в данном случае требуется проверка типа во время выполнения).
Так как структуры не являются ссылочными типами, эти операции реализуются по-разному для типов структур. При преобразовании значения типа структуры в определенные ссылочные типы (как определено в §10.2.9), выполняется операция бокса. Аналогичным образом, когда значение определенных ссылочных типов (как определено в §10.3.7) преобразуется обратно в тип структуры, выполняется операция распаковки. Ключевое отличие от одних и тех же операций с типами классов заключается в том, что бокс и распаковка копирует значение структуры либо в боксовый экземпляр, либо из него.
Примечание. Таким образом, после операции бокса или распаковки изменения, внесенные в распаковку
struct
, не отражаются в коробкеstruct
. конечная заметка
Дополнительные сведения о боксе и распаковки см. в статьях 10.2.9 и §10.3.7.
Значение 16.4.7
Значение структуры отличается от значения this
this
класса, как описано в §12.8.14. Если тип структуры переопределяет виртуальный метод, унаследованный от System.ValueType
(напримерEquals
GetHashCode
, илиToString
), вызов виртуального метода через экземпляр типа структуры не приводит к возникновению бокса. Это верно, даже если структура используется в качестве параметра типа, и вызов происходит через экземпляр типа параметра типа.
Пример:
struct Counter { int value; public override string ToString() { value++; return value.ToString(); } } class Program { static void Test<T>() where T : new() { T x = new T(); Console.WriteLine(x.ToString()); Console.WriteLine(x.ToString()); Console.WriteLine(x.ToString()); } static void Main() => Test<Counter>(); }
Выходные данные программы:
1 2 3
Хотя это плохой стиль для
ToString
того чтобы иметь побочные эффекты, в примере показано, что бокс не произошел для трех вызововx.ToString()
.пример конца
Аналогичным образом, бокс никогда неявно происходит при доступе к члену в параметре ограниченного типа, когда член реализуется в типе значения. Например, предположим, что интерфейс ICounter
содержит метод Increment
, который можно использовать для изменения значения. Если ICounter
используется в качестве ограничения, реализация Increment
метода вызывается со ссылкой на переменную, Increment
которая была вызвана, никогда не в поле.
Пример:
interface ICounter { void Increment(); } struct Counter : ICounter { int value; public override string ToString() => value.ToString(); void ICounter.Increment() => value++; } class Program { static void Test<T>() where T : ICounter, new() { T x = new T(); Console.WriteLine(x); x.Increment(); // Modify x Console.WriteLine(x); ((ICounter)x).Increment(); // Modify boxed copy of x Console.WriteLine(x); } static void Main() => Test<Counter>(); }
Первый вызов
Increment
изменения значения в переменнойx
. Это не эквивалентно второму вызовуIncrement
, который изменяет значение в прямоугольной копииx
. Таким образом, выходные данные программы:0 1 1
пример конца
Инициализаторы полей 16.4.8
Как описано в §16.4.5, значение по умолчанию структуры состоит из значения, которое приводит к настройке всех полей типа значения в значение по умолчанию и всех полей null
ссылочного типа. По этой причине структуру не позволяет объявлениям полей экземпляра включать инициализаторы переменных. Это ограничение применяется только к полям экземпляра. Статические поля структуры разрешены включать инициализаторы переменных.
Пример. Ниже приведено следующее
struct Point { public int x = 1; // Error, initializer not permitted public int y = 1; // Error, initializer not permitted }
ошибка, так как объявления полей экземпляра включают инициализаторы переменных.
пример конца
Конструкторы 16.4.9
В отличие от класса, структуру нельзя объявить конструктор экземпляра без параметров. Вместо этого каждая структура неявно имеет конструктор экземпляра без параметров, который всегда возвращает значение, которое приводит к настройке всех полей типа значения по умолчанию и всех полей null
ссылочного типа (§8.3.3). Структуру можно объявить конструкторы экземпляров с параметрами.
Пример. Учитывая следующее:
struct Point { int x, y; public Point(int x, int y) { this.x = x; this.y = y; } } class A { static void Main() { Point p1 = new Point(); Point p2 = new Point(0, 0); } }
операторы создают и
Point
x
инициализированыy
до нуля.пример конца
Конструктор экземпляра структуры не может включать инициализатор конструктора формы base(
argument_list)
, где argument_list является необязательным.
Параметр this
конструктора экземпляра структуры соответствует выходному параметру типа структуры. Таким образом, this
должно быть определенно назначено (§9.4) в каждом расположении, где возвращается конструктор. Аналогичным образом, его нельзя читать (даже неявно) в теле конструктора перед определенным назначением.
Если конструктор экземпляра структуры задает инициализатор конструктора, этот инициализатор считается определенным назначением для этого, которое происходит до текста конструктора. Поэтому сам текст не имеет требований к инициализации.
Пример. Рассмотрим реализацию конструктора экземпляра ниже:
struct Point { int x, y; public int X { set { x = value; } } public int Y { set { y = value; } } public Point(int x, int y) { X = x; // error, this is not yet definitely assigned Y = y; // error, this is not yet definitely assigned } }
Никакие члены функции экземпляра (включая наборы доступа для свойств
X
иY
) не могут вызываться до тех пор, пока не будут назначены все поля создаваемой структуры. Обратите внимание, что если классPoint
был не структурой, реализация конструктора экземпляра будет разрешена. Существует одно исключение, и это включает автоматически реализованные свойства (§15.7.4). Определенные правила назначения (§12.21.2) специально освобождают назначение для автоматического свойства типа структуры в конструкторе экземпляра этого типа структуры: такое назначение считается определенным назначением скрытого поля резервного поля автоматического свойства. Таким образом, допускается следующее:struct Point { public int X { get; set; } public int Y { get; set; } public Point(int x, int y) { X = x; // allowed, definitely assigns backing field Y = y; // allowed, definitely assigns backing field } }
пример конца]
16.4.10 Статические конструкторы
Статические конструкторы для структур следуют большинству правил, что и для классов. Выполнение статического конструктора для типа структуры активируется первым из следующих событий, происходящих в домене приложения:
- Ссылка на статический элемент типа структуры.
- Вызывается явно объявленный конструктор типа структуры.
Примечание. Создание значений по умолчанию (§16.4.5) типов структур не активирует статический конструктор. (Примером этого является начальное значение элементов в массиве.) конечная заметка
16.4.11 Автоматически реализованные свойства
Автоматически реализованные свойства (§15.7.4) используют скрытые поля резервной копии, которые доступны только для методов доступа к свойствам.
Примечание. Это ограничение доступа означает, что конструкторы в конструкциях, содержащих автоматически реализованные свойства, часто нуждаются в явном инициализаторе конструктора, где они не нуждаются в одном из них, для удовлетворения требования всех полей, определенно назначенных перед вызовом любого члена функции или возвратом конструктора. конечная заметка
16.4.12 Безопасное ограничение контекста
16.4.12.1 General
Во время компиляции каждое выражение связано с контекстом, где этот экземпляр и все его поля можно безопасно получить к ним доступ, его безопасный контекст. Безопасный контекст — это контекст, включающий выражение, которое безопасно для того, чтобы значение не бежало.
Любое выражение, тип времени компиляции которого не является структурой ссылок, имеет безопасный контекст вызывающего контекста.
Выражение default
любого типа имеет безопасный контекст вызывающего контекста.
Для любого выражения, не используемого по умолчанию, тип компиляции которого является структурой ссылок, имеет безопасный контекст, определенный следующими разделами.
Записи безопасного контекста, в которые может быть скопировано значение. Учитывая назначение из выражения с безопасным контекстом, в выражение E1
E2
с безопасным контекстом, это ошибка, если S2
это более широкий контекстS2
S1
.S1
Существует три разных значения безопасного контекста, такие же, как значения ref-safe-context, определенные для ссылочных переменных (§9.7.2): объявление-блок, член-функция и вызывающий контекст. Безопасный контекст выражения ограничивает его использование следующим образом:
- Для инструкции
return e1
return безопасный контекстe1
должен быть вызывающим контекстом. - Для назначения
e1 = e2
безопасного контекстаe2
должно быть по крайней мере как широкий контекст, так как безопасный контекстe1
.
Для вызова метода, если существует ref
или out
аргумент ref struct
типа (включая приемник, если тип не имеется readonly
), с безопасным контекстом, то аргумент (включая приемник) может иметь более узкий безопасный контекст S1
, чем S1
.
Безопасный контекст параметра 16.4.12.2
Параметр типа структуры ссылок, включая this
параметр метода экземпляра, имеет безопасный контекст вызывающего контекста.
16.4.12.3 Безопасный контекст локальной переменной
Локальная переменная типа структуры ссылок имеет безопасный контекст следующим образом:
- Если переменная является переменной
foreach
итерации цикла, то безопасный контекст переменной совпадает с безопасным контекстомforeach
выражения цикла. - В противном случае, если объявление переменной имеет инициализатор, безопасный контекст переменной совпадает с безопасным контекстом этого инициализатора.
- В противном случае переменная неинициализирована в точке объявления и имеет безопасный контекст вызывающего контекста.
Безопасный контекст поля 16.4.4.4
Ссылка на поле e.F
, где тип F
структуры является типом структуры ссылок, имеет безопасный контекст, который совпадает с безопасным контекстом e
.
Операторы 16.4.12.5
Приложение определяемого пользователем оператора рассматривается как вызов метода (§16.4.12.6).
Для оператора, который дает значение, например e1 + e2
или c ? e1 : e2
, безопасный контекст результата является самым узким контекстом среди безопасных контекстов операндов оператора. В результате для унарного оператора, который дает значение, например +e
, безопасный контекст результата является безопасным контекстом операнда.
Примечание. Первый операнды условного оператора — это ,
bool
поэтому его безопасный контекст является вызывающим контекстом. Следует, что результирующий безопасный контекст является самым узким безопасным контекстом второго и третьего операнда. конечная заметка
Метод и вызов свойств 16.4.12.6
Значение, полученное из вызова e1.M(e2, ...)
метода или вызова e.P
свойства, имеет безопасный контекст наименьшего из следующих контекстов:
- вызывающий контекст.
- Безопасный контекст всех выражений аргументов (включая приемник).
Вызов свойства (или get
set
) рассматривается как вызов метода базового метода с помощью приведенных выше правил.
16.4.12.7 stackalloc
Результат выражения stackalloc имеет безопасный контекст элемента-функции.
Вызовы конструктора 16.4.12.8
new
Выражение, вызывающее конструктор, подчиняется тем же правилам, что и вызов метода, который считается возвращаемым типом.
Кроме того, безопасный контекст является наименьшим из безопасных контекстов всех аргументов и операндов всех выражений инициализатора объектов, рекурсивно, если какой-либо инициализатор присутствует.
Примечание. Эти правила полагаются на
Span<T>
отсутствие конструктора следующей формы:public Span<T>(ref T p)
Такой конструктор делает экземпляры используемых
Span<T>
в качестве полей неотличимыми отref
поля. Правила безопасности, описанные в этом документе, зависят отref
полей, которые не имеют допустимой конструкции в C# или .NET. конечная заметка
ECMA C# draft specification