Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
16.1 Общие положения
Структуры похожи на классы, представляющие структуры данных, которые могут содержать элементы данных и члены функции. Однако, в отличие от классов, структуры являются типами значений и не требуют выделения кучи. Переменная типа struct напрямую содержит данные struct, тогда как переменная типа класса содержит ссылку на данные, причем последняя называется объектом.
Примечание. Структуры особенно полезны для небольших структур данных, имеющих семантику значений. Хорошими примерами структур можно считать комплексные числа, точки в системе координат или словари с парами ключ-значение. Ключом к этим структурам данных является то, что они имеют несколько элементов данных, которые не требуют использования семантики наследования или ссылки, а могут быть удобно реализованы семантикой значений, где назначение копирует значение вместо ссылки. конечная заметка
Как описано в §8.3.5, простые типы, предоставляемые C#, такие как int, doubleи bool, на самом деле, являются всеми типами структур.
Объявления структур, раздел 16.2
16.2.1 Общие
Struct_declaration — это type_declaration (§14.8), которая объявляет новую структуру:
struct_declaration
: non_record_struct_declaration
| record_struct_declaration
;
non_record_struct_declaration
: attributes? struct_modifier* 'ref'? 'partial'? 'struct'
identifier type_parameter_list? struct_interfaces?
type_parameter_constraints_clause* struct_body ';'?
;
record_struct_declaration
: attributes? struct_modifier* 'partial'? 'record' 'struct'
identifier type_parameter_list? delimited_parameter_list? struct_interfaces?
type_parameter_constraints_clause* record_struct_body
;
record_struct_body
: struct_body ';'?
| ';'
;
Struct_declaration используется либо для структуры, отличной от записи, либо для структуры записи.
Non_record_struct_declaration состоит из необязательного набора атрибутов (§23), за которым следует необязательный набор struct_modifiers (§16.2.2), а затем необязательный ref модификатор (§16.2.3), за которым следует необязательный частичный модификатор (§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), за которым при необходимости следует точка с запятой.
Record_struct_declaration состоит из необязательного набора атрибутов (§23), за которым следует необязательный набор struct_modifiers (§16.2.2), а затем необязательный частичный модификатор (§15.2.7), за которым следует ключевое слово recordstruct, а затем идентификатор, который называет структуру, а затем необязательной спецификацией type_parameter_list (§15.2.3), за которой следует необязательный delimited_parameter_list спецификация (§15.2.1), за которой следует необязательная спецификация struct_interfaces (§16.2.5), за которой следует дополнительная спецификация type_parameter_constraints-предложения (§15.2.5), за которой следует record_struct_body.
Struct_declaration не должны предоставлять type_parameter_constraints_clause, если он также не предоставляет type_parameter_list.
Struct_declaration, предоставляющий type_parameter_list, является универсальным объявлением структуры. Кроме того, любая структура, вложенная в объявление универсального класса или универсальное объявление структуры, является универсальным объявлением структуры, так как аргументы типов для содержащего типа должны быть предоставлены для создания созданного типа (§8.4).
Non_record_struct_declaration, включающую ref модификатор, не должен иметь struct_interfaces часть.
Record_struct_declaration с delimited_parameter_list объявляет структуру позиционной записи.
Не более одного record_struct_declaration, содержащего partialdelimited_parameter_list.
Параметры в delimited_parameter_list не должны иметь refили outthis модификаторы, однако inparams и модификаторы разрешены.
Для record_struct_declaration record_struct_body {}{};и ; эквивалентны. Все они указывают на то, что единственными элементами, синтезируемыми компилятором (§16.4).
Модификаторы структуры 16.2.2
Опционально struct_declaration может включать последовательность struct_modifier:
struct_modifier
: 'new'
| 'public'
| 'protected'
| 'internal'
| 'private'
| 'readonly'
| 'file'
| unsafe_modifier // unsafe code support
;
unsafe_modifier (§24.2) доступен только в небезопасном коде (§24).
Это ошибка компиляции, если один и тот же модификатор появляется несколько раз в объявлении структуры.
Это ошибка во время компиляции для любого из publicprotectedмодификаторов , internalи private модификаторов, которые следует объединить с модификаторомfile.
За исключением readonly, модификаторы объявления структуры имеют то же значение, что и объявление класса (§15.2.2).
Модификатор readonly указывает, что struct_declaration объявляет тип, экземпляры которого неизменяемы.
Структура только для чтения имеет следующие ограничения:
- Каждое из полей объекта также должно быть объявлено
readonly. - Оно не должно объявлять какие-либо события, подобные полю (§15.8.2).
Когда экземпляр структуры только для чтения передается методу, она this обрабатывается как входной аргумент/параметр, что запрещает запись в любые поля экземпляра (за исключением случаев, когда это делается конструкторами).
Модификатор ref 16.2.3
Модификатор ref указывает, что non_record_struct_declaration объявляет тип, экземпляры которого выделяются в стеке выполнения. Эти типы называются типами структур ссылок . Модификатор ref объявляет, что экземпляры могут содержать поля ссылок и не должны быть скопированы из его безопасного контекста (§16.5.15). Правила определения безопасного контекста структуры ссылок описаны в разделе 16.5.15.
Это ошибка во время компиляции, если тип структуры ref используется в любом из следующих контекстов:
- В качестве типа элемента массива.
- Как объявленный тип поля класса или структуры, у которых нет
refмодификатора. - В качестве аргумента типа.
- Тип элемента кортежа.
- В асинхронном методе.
- В итераторе.
- Как тип приемника для преобразования группы методов из метода экземпляра в тип делегата.
- Как записанная переменная в лямбда-выражении или локальной функции.
Кроме того, к типу ref struct применяются следующие ограничения:
- Тип
ref structне должен быть установлен вSystem.ValueTypeполе илиSystem.Object. -
ref structТип не должен быть объявлен для реализации любого интерфейса. - Метод экземпляра, объявленный в
objectилиSystem.ValueType, но не переопределён в типеref struct, не должен вызываться с объектом этогоref structтипа.
Примечание:
ref structне должен объявлятьasyncметоды экземпляра, а также использоватьyield returnилиyield breakинструкцию внутри метода экземпляра, потому что неявный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.
Реализации интерфейса рассматриваются далее в §19.6.
Тело структуры 16.2.6
Struct_body структуры определяет элементы структуры.
struct_body
: '{' struct_member_declaration* '}'
;
Элементы структуры 16.3
16.3.1 Общие
Члены структуры состоят из элементов, представленных его struct_member_declarationи членами, унаследованными от типа System.ValueType. Для структуры записи набор элементов также включает синтезированные элементы, созданные компилятором (§synth-members).
struct_member_declaration
: constant_declaration
| struct_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 (§24.8.2) доступен только в небезопасном коде (§24).
Примечание. Struct_member_declaration включает все альтернативные class_member_declaration , кроме finalizer_declaration, и добавляет struct_field_declaration , которая поддерживает поля ссылок (§16.5.8.2). конечная заметка
Поля в структуре поддерживают возможности, которые не поддерживаются в классах. Дополнительные сведения см. в разделе "16.5.8.2 ".
За исключением различий, указанных в §16.5, описания членов класса, предоставленных в §15.3 до §15.12, применяются к элементам структуры.
Это ошибка для поля экземпляра структуры записи с небезопасным типом.
16.3.2 Члены Readonly
Определение элемента экземпляра или метод доступа к свойству экземпляра, индексатору или событию, включающее readonly модификатор, имеет следующие ограничения:
- Параметр
thisявляется ссылкойref readonly. - Член не должен переназначить значение
thisили поле экземпляра получателя. - Член не должен переназначить значение события типа экземпляра (§15.8.2) получателя.
- Если элемент чтения вызывает нечитаемый член, структура, на которую ссылается
thisссылка, должна быть скопирована для использования записываемой ссылки для аргументаthis.
Примечание: Поля экземпляров включают скрытое поле резервного копирования, используемое для автоматически реализованных свойств (§15.7.4). конечная заметка
Пример. Элемент readonly может изменить состояние объекта, на который ссылается поле экземпляра, даже если элемент readonly не может переназначить этот элемент экземпляра. Следующий код демонстрирует переназначение и изменение поля экземпляра:
public struct S { private List<string> messages; public S(IEnumerable<string> messages) => this.messages = new List<string>(messages); public void InitializeMessages() => messages = new List<string>(); public readonly void AddMessage(string message) { if (messages == null) { throw new InvalidOperationException("Messages collection is not initialized."); } messages.Add(message); } }Метод
readonlyAddMessageможет изменить состояние списка сообщений. ЭлементInitializeMessagesможет очистить и повторно инициализировать список сообщений. В случаеAddMessageсreadonlyмодификатором является допустимым. В случаеInitializeMessagesдобавленияreadonlyмодификатора недопустимо. конец примера
16.4 Синтезированные элементы структуры записей
16.4.1 Общие
В случае структуры записи члены синтезируются, если член с подписью matching не объявлен в record_struct_body или доступный конкретный не-виртуальный член с подписью сопоставления наследуется. Два члена считаются соответствующими, если они имеют одну и ту же подпись или считаются "скрытием" в сценарии наследования. (См. подписи и перегрузки §7.6.)
Синтезированные члены описаны в следующих подклаузах.
16.4.2 члены равенства
Синтезированные члены равенства похожи на элементы для класса записей (§15.16.2), за исключением отсутствия EqualityContract, проверки null или наследования.
Структуру записей R реализуется System.IEquatable<R> и включает синтезированную строго типизированную перегрузку Equals(R other), которая является общедоступной, как показано ниже.
public readonly bool Equals(R other);
Этот метод можно объявить явным образом. Однако это ошибка, если явное объявление не соответствует ожидаемой сигнатуре или специальным возможностям.
Если Equals(R other) пользователь определен (то есть не синтезирован), но GetHashCode не является, предупреждение должно быть создано.
Синтезированный Equals(R) объект возвращает значениеtrue, если и только если для каждого поля fieldN экземпляра в записи указано значение System.Collections.Generic.EqualityComparer<TN>.Default.Equals(fieldN, other.fieldN)типа поляTN.true
Структура записи включает синтезированные и == операторы, эквивалентные != операторам, объявленным следующим образом:
public static bool operator==(R r1, R r2) => r1.Equals(r2);
public static bool operator!=(R r1, R r2) => !(r1 == r2);
Метод Equals , вызываемый оператором == , является методом Equals(R other) , указанным выше. Оператор != делегирует оператору == . Это ошибка, если операторы объявляются явно.
Структуру записи включает синтезированный переопределение, эквивалентный методу, объявленному следующим образом:
public override readonly bool Equals(object? obj);
Это ошибка, если переопределение объявляется явным образом. Синтезированный переопределение должно возвращать other is R temp && Equals(temp) , где R находится структура записи.
Структуру записи включает синтезированный переопределение, эквивалентный методу, объявленному следующим образом:
public override readonly int GetHashCode();
Этот метод может быть объявлен явным образом.
Предупреждение должно быть сообщено, если один из Equals(R) них явно объявлен, GetHashCode() но другой метод не является.
Синтезированный переопределение GetHashCode() возвращает int результат объединения значений System.Collections.Generic.EqualityComparer<TN>.Default.GetHashCode(fieldN) для каждого поля fieldN экземпляра с TN типом fieldN.
Пример. Рассмотрим следующую структуру записи:
record struct R1(T1 P1, T2 P2);Для этого синтезированные члены равенства будут примерно следующим образом:
struct R1 : IEquatable<R1> { public T1 P1 { get; set; } public T2 P2 { get; set; } public override bool Equals(object? obj) => obj is R1 temp && Equals(temp); public bool Equals(R1 other) { return EqualityComparer<T1>.Default.Equals(P1, other.P1) && EqualityComparer<T2>.Default.Equals(P2, other.P2); } public static bool operator==(R1 r1, R1 r2) => r1.Equals(r2); public static bool operator!=(R1 r1, R1 r2) => !(r1 == r2); public override int GetHashCode() { return HashCode.Combine( EqualityComparer<T1>.Default.GetHashCode(P1), EqualityComparer<T2>.Default.GetHashCode(P2));конец примера
16.4.3 Элементы печати
Структуру записи включает синтезированный метод, эквивалентный следующему:
private bool PrintMembers(System.Text.StringBuilder builder);
Этот метод выполняет следующие задачи:
- Для каждого из печатных элементов структуры записи (нестатическое открытое поле и члены доступных для чтения свойств) добавляет имя этого члена, за которым следует "
=" и значение члена, разделенное ", с ", “, - Возвращает значение true, если у структуры записи есть печатные элементы.
Для элемента, имеющего тип значения, его значение должно быть преобразовано в строковое представление.
Если элементы записей, доступные для печати, не включают доступное для чтения свойство с не-доступомreadonlyget , синтезируется PrintMembersreadonly. Для этого метода readonlyне требуется использовать поля PrintMembersreadonly записи.
Метод PrintMembers можно объявить явным образом. Однако это ошибка, если явное объявление не соответствует ожидаемой сигнатуре или специальным возможностям.
Структуру записи включает синтезированный метод, эквивалентный следующему:
public override string ToString();
Если метод структуры PrintMembers записи имеет значение readonly, то синтезированный ToString() метод должен быть readonly.
Этот метод можно объявить явным образом. Это ошибка, если явное объявление не соответствует ожидаемой сигнатуре или специальным возможностям.
Этот метод выполняет следующие задачи:
-
StringBuilderСоздает экземпляр, - Добавляет имя структуры записи в построитель, за которым следует "
{", - Вызывает метод структуры
PrintMembersзаписи, предоставляющий ему построителя, а затем "", если он вернул значение true, - Добавляет "
}", - Возвращает содержимое построителя с
builder.ToString().
Пример. Рассмотрим следующую структуру записи:
record struct R1(T1 P1, T2 P2);Для этой структуры записи синтезированные элементы печати будут примерно следующим образом:
struct R1 : IEquatable<R1> { public T1 P1 { get; set; } public T2 P2 { get; set; } private bool PrintMembers(StringBuilder builder) { builder.Append(nameof(P1)); builder.Append(" = "); builder.Append(this.P1); // or builder.Append(this.P1.ToString()); // if P1 has a value type builder.Append(", "); builder.Append(nameof(P2)); builder.Append(" = "); builder.Append(this.P2); // or builder.Append(this.P2.ToString()); // if P2 has a value type return true; } public override string ToString() { var builder = new StringBuilder(); builder.Append(nameof(R1)); builder.Append(" { "); if (PrintMembers(builder)) builder.Append(" "); builder.Append("}"); return builder.ToString(); } }конец примера
Элементы структуры позиционной записи 16.4.4
16.4.4.1 Общие
Кроме того, как предоставить членам, описанным в предыдущих подклаузах, структуры позиционной записи (§16.2.1) синтезируют дополнительные элементы с теми же условиями, что и другие члены, как описано в следующих подклаузах.
16.4.4.2 Первичный конструктор
У структуры записи есть открытый конструктор, подпись которого соответствует параметрам значения объявления типа. Это называется основным конструктором для типа. Это ошибка с основным конструктором и конструктором с той же сигнатурой, уже присутствующей в структуре. Если объявление типа не содержит delimited_parameter_list, основной конструктор не создается.
record struct R1 { public R1() { } // OK } record struct R2() { public R2() { } // error: 'R2' already defines // a constructor with the same parameter types }
Объявления полей экземпляра для структуры записей разрешены включать инициализаторы переменных. Если основной конструктор отсутствует, инициализаторы экземпляров выполняются как часть конструктора без параметров. В противном случае во время выполнения основной конструктор выполняет инициализаторы экземпляров, отображаемые в тексте записи-структуры.
Если у структуры записи есть первичный конструктор, любой определяемый пользователем конструктор должен иметь явный this инициализатор конструктора, который вызывает первичный конструктор или явно объявленный конструктор.
Параметры основного конструктора, а также члены структуры записи находятся в области инициализаторов полей или свойств экземпляра. Члены экземпляра будут ошибкой в этих расположениях, но параметры основного конструктора будут находиться в области и использовать и будут теневыми элементами. Статические члены также будут использоваться.
Предупреждение должно быть создано, если параметр первичного конструктора не считывается.
Определенные правила назначения для конструкторов экземпляров структуры применяются к основному конструктору структур записей. Например, следующая ошибка:
record struct Pos(int X) // def assignment error in primary constructor { private int x; public int X { get { return x; } set { x = value; } } = X; }
Свойства 16.4.4.3
Для каждого параметра delimited_parameter_list , имеющего то же имя и тип, что и поле явно объявленного экземпляра, оставшаяся часть этого подклауза не применяется.
Для каждого параметра структуры записи delimited_parameter_list существует соответствующий элемент общедоступного свойства, имя и тип которого взяты из объявления параметра значения.
Для структуры записи:
Создается общедоступное
getиinitавтоматическое свойство, если структура записи имеетreadonlyмодификатор иgetвsetпротивном случае. Оба типа наборов доступа (setиinit) считаются "сопоставлением". Таким образом, пользователь может объявить свойство только для инициализации вместо синтезированного мутируемого.Унаследованное
abstractсвойство с соответствующим типом переопределяется.Автоматическое свойство не создается, если у структуры записи есть поле экземпляра с ожидаемым именем и типом.
Это ошибка, если унаследованное свойство не имеет
publicgetиset/initметоды доступа.Это ошибка, если унаследованное свойство или поле скрыто.
Автоматическое свойство инициализируется значением соответствующего основного параметра конструктора.
Атрибуты могут применяться к синтезируемой автоматической свойстве и его резервному полю с помощью
property:илиfield:целевым объектам для атрибутов синтаксически применены к соответствующему параметру структуры записи.
Деконструкция 16.4.4
Структуру позиционной записи с по крайней мере одним параметром синтезирует открытый voidметод возвращающего экземпляра, вызываемого Deconstruct с объявлением параметра out для каждого параметра основного объявления конструктора. Каждый параметр имеет тот же тип, что и соответствующий параметр Deconstruct основного объявления конструктора. Текст метода назначает каждому параметру метода Деконструкция значению из доступа члена экземпляра к элементу того же имени.
Если члены экземпляра, доступ к которым осуществляется в тексте, не включают свойство с не-методомreadonlyget доступа, то синтезированный Deconstruct метод .readonly
Метод можно объявить явным образом. Это ошибка, если явное объявление не соответствует ожидаемой сигнатуре или специальным возможностям или является статическим.
Различия классов и структур 16.5
16.5.1 Общие
Структуры отличаются от классов несколькими важными способами:
- Структуры — это типы значений (§16.5.2).
- Все типы структур неявно наследуются от класса
System.ValueType(§16.5.3). - Назначение переменной типа структуры создает копию присваиваемого значения (§16.5.4).
- Значение структуры по умолчанию — это значение, созданное путем установки всех полей в значение по умолчанию (§16.5.5).
- Операции бокса и распаковки используются для преобразования между типом структуры и определенными ссылочными типами (§16.5.6).
- Смысл отличается в элементах
thisструктуры (§16.5.7). - Структурам не разрешено объявлять финализаторы.
- Объявления событий, объявления свойств, методы доступа к свойствам, объявления индексатора и объявления методов могут иметь модификатор
readonly, хотя это не разрешено для тех же типов элементов в классах.
Семантика значения 16.5.2
Структуры — это типы значений (§8.3) и имеют семантику значений. Классы, с другой стороны, являются ссылочными типами (§8.2) и, как говорят, имеют ссылочные семантики.
Переменная типа структуры напрямую содержит данные структуры, в то время как переменная типа класса содержит ссылку на объект, содержащий данные. Если структура B содержит поле экземпляра типа A, и A является структурным типом, то ошибка времени компиляции возникает, если A зависит от B или типа, созданного на основе B. Структура 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иBCзависит друг от друга.конец примера
Классы позволяют двум переменным ссылаться на один и тот же объект и, следовательно, могут влиять на объект, на который ссылается другая переменная. При использовании структур каждая переменная имеет собственную копию данных (кроме случаев, когда параметры передаются по ссылке), и операции с одной из них не могут повлиять на другую. Кроме того, за исключением случаев, когда явно допускается значение 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к . Если быPointбыло объявлено как класс, выходные данные были бы100, потому чтоaиbбудут ссылаться на один и тот же объект.конец примера
Наследование 16.5.3
Все типы структур неявно наследуются от класса System.ValueType, который, в свою очередь, наследует от класса object. Объявление структуры может указывать список реализованных интерфейсов, но объявление структуры невозможно указать базовый класс.
Типы структур никогда не являются абстрактными и всегда неявно запечатаны. Поэтому модификаторы abstract и sealed не допускаются в объявлении структуры.
Так как наследование не поддерживается для структур, объявленная доступность элемента структуры не может быть protected, private protectedили protected internal.
Функции в структуре не могут быть абстрактными или виртуальными, и модификатор override разрешается только для переопределения методов, унаследованных от System.ValueType.
Назначение 16.5.4
Назначение переменной типа структуры создает копию присваиваемого значения. Это отличается от назначения переменной типа класса, которая копирует ссылку, но не объект, определенный ссылкой.
Подобно присваиванию, когда структура передается как параметр значения или возвращается в результате функции-члена, создается копия структуры. Структуру можно передать в функцию посредством параметра, передаваемого по ссылке.
Если свойство или индексатор структуры является целью назначения, выражение экземпляра, связанное со свойством или доступом индексатора, должно классифицироваться как переменная. Если выражение экземпляра классифицируется как значение, возникает ошибка во время компиляции. Это подробно описано в разделе 12.24.2.
Значения по умолчанию 16.5.5
Как описано в разделе 9.3, при создании переменных несколько типов переменных автоматически инициализированы в их значение по умолчанию. Для переменных типов классов и других ссылочных типов, а также полей ссылочных переменных это значение nullпо умолчанию. Однако, поскольку структуры являются типами значений, которые не могут быть null, значение по умолчанию структуры является значением, созданным путем установки всех полей типа значения по умолчанию и всех полей ссылочных переменных и полей nullссылочного типа.
Пример: ссылка на структуру
Point, объявленную выше, примерPoint[] a = new Point[100];инициализирует каждый
Pointв массиве значением, полученным путем установки полейxиyв ноль.конец примера
Значение по умолчанию структуры соответствует значению, возвращаемого конструктором структуры по умолчанию (§8.3.3). Если структура не объявляет явный конструктор экземпляра без параметров, конструктор по умолчанию синтезируется и всегда возвращает значение, которое приводит к настройке всех полей значениям по умолчанию. Выражение default всегда создает нулевое инициализированное значение по умолчанию, даже если структура объявляет явный конструктор экземпляра без параметров (§16.4.9).
Примечание. Структуры должны быть разработаны для рассмотрения состояния инициализации по умолчанию допустимого состояния. В примере
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, а также структура, должны быть подготовлены для обработки этого состояния.конечная заметка
16.5.6 Бокс и распаковка
Значение типа класса можно преобразовать в тип object или в тип интерфейса, реализуемый классом, просто рассматривая ссылку как другой тип во время компиляции. Аналогичным образом, значение типа object или значения типа интерфейса можно преобразовать обратно в тип класса, не изменив ссылку (но, конечно, в данном случае требуется проверка типа во время выполнения).
Так как структуры не являются ссылочными типами, эти операции реализуются по-разному для типов структур. При преобразовании значения типа структуры в определенные ссылочные типы (как указано в §10.2.9), выполняется упаковка значения. Аналогичным образом, когда значение определенных ссылочных типов (как определено в §10.3.7) преобразуется обратно в тип структуры, выполняется операция распаковки. Ключевое отличие от одних и тех же операций с типами классов заключается в том, что упаковка и распаковка копируют значение структуры либо в боксовый экземпляр, либо из него.
Примечание. Таким образом, после операции упаковки или распаковки изменения, внесенные в распакованный
struct, не находят отражения в упакованномstruct. конечная заметка
Для получения дополнительных сведений об упаковке и распаковке см. в разделах §10.2.9 и §10.3.7.
Значение 16.5.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.5.8
Инициализаторы полей 16.5.8.1
Как описано в разделе 16.5.5, значение по умолчанию структуры состоит из значения, которое приводит к настройке всех полей значений и ссылочных переменных в значение по умолчанию и всех полей nullссылочного типа. Статические и экземплярные поля структуры разрешены включать инициализаторы переменных; однако в случае инициализатора поля экземпляра также должен быть объявлен по крайней мере один конструктор экземпляра или для структуры записи, delimited_parameter_list должен присутствовать.
Пример:
Console.WriteLine($"Point is {new Point()}"); struct Point { public int x = 1; public int y = 1; public Point() { } public override string ToString() { return "(" + x + ", " + y + ")"; } }Point is (1, 1)конец примера
Если конструктор экземпляра структуры не имеет инициализатора конструктора, конструктор неявно выполняет инициализации, указанные variable_initializerполей экземпляра, объявленных в его структуре. Это соответствует последовательности назначений, которые выполняются сразу после входа в конструктор.
Когда конструктор экземпляра структуры имеет this() инициализатор конструктора, представляющий конструктор без параметров по умолчанию, объявленный конструктор неявно очищает все поля экземпляров и выполняет инициализации, указанные variable_initializerполя экземпляра, объявленные в ее структуре. Сразу после входа в конструктор все поля типа значения задаются значением по умолчанию, а для всех полей ссылочного типа задано значение null. Сразу после этого выполняется последовательность назначений, соответствующих variable_initializer.
field_declaration, объявленный непосредственно внутри struct_declaration с использованием struct_modifierreadonly, должен иметь field_modifierreadonly.
Поля ссылок 16.5.8.2
struct_field_declaration
: attributes? field_modifier* ('readonly'? 'ref' 'readonly'?)? type
variable_declarators ';'
;
field_modifier описано в разделе §15.5.1.
Struct_field_declaration без ref, readonly refили ref readonly как описано в §15.5.
Или ref поле является ссылочной переменной и должно быть объявлено только в refreadonly ref структуре.
Рассмотрим следующее объявление структуры ссылок:
ref struct RwS
{
public static int rwField = 100;
public ref int rwRefToRwData = ref rwField;
public ref readonly int rwRefToRoData = ref rwField;
public readonly ref int roRefToRwData = ref rwField;
public readonly ref readonly int roRefToRoData = ref rwField;
public RwS() { /*…*/ }
}
rwRefToRwData — это записываемая ссылочная переменная, референт которой рассматривается как записываемая int.
rwRefToRoData — это записываемая ссылочная переменная, референт которой рассматривается как доступный только для intчтения.
roRefToRwData — это переменная ссылки только для чтения, референт которой рассматривается как доступный intдля записи.
roRefToRoData — это переменная ссылки только для чтения, референт которой рассматривается как доступный только для intчтения. Поле rwField чтения и записи можно записывать напрямую, а также с помощью ссылочных переменных rwRefToRwData и roRefToRwData.
Переменная ссылки на чтение может принимать значение через инициализатор или через назначение внутри конструктора или инициализатора.
Рассмотрим следующее объявление структуры refonly:
readonly ref struct RoS
{
public static int rwField = 200;
public readonly ref int roRefToRwData = ref rwField;
public readonly ref readonly int roRefToRoData = ref rwField;
public RoS() { /*...*/ }
}
roRefToRwData — это переменная ссылки только для чтения, референт которой рассматривается как доступный intдля записи.
roRefToRoData — это переменная ссылки только для чтения, референт которой рассматривается как доступный только для intчтения. Поле rwField чтения и записи можно записывать напрямую и с помощью эталонной переменной roRefToRwData.
Конструкторы 16.5.9
Структуру можно объявить конструкторы экземпляров с нулевыми или более параметрами. Если у структуры нет явно объявленного конструктора экземпляра без параметров, он синтезируется с общедоступным специальными возможностями, которое всегда возвращает значение, которое приводит к настройке всех полей типа значения в значение по умолчанию, все поля ссылочной переменной значением NULL и все поля null ссылочного типа в (§8.3.3). В таком случае все инициализаторы полей экземпляра игнорируются при выполнении этого конструктора.
Явным образом объявленный конструктор экземпляра без параметров должен иметь общедоступную доступность.
Пример: учитывая следующее:
using System; struct Point { int x = -1, y = -2; public Point(int x, int y) { this.x = x; this.y = y; } public override string ToString() { return "(" + x + ", " + y + ")"; } } class A { static void Main() { Console.WriteLine($"Point is {new Point()}"); Console.WriteLine($"Point is {new Point(0,0)}"); } }Point is (0, 0) Point is (0, 0)Операторы создают
Pointиxyинициализированы до нуля, что в случае вызова конструктора экземпляра без параметров может быть удивительно, так как оба поля экземпляра имеют инициализаторы, но они не выполняются.конец примера
Конструктор экземпляра структуры не может включать инициализатор конструктора формы base(argument_list), где argument_list является необязательным. Выполнение конструктора экземпляра не должно привести к выполнению конструктора в базовом типе System.ValueTypeструктуры.
Параметр this конструктора экземпляра структуры соответствует выходному параметру типа структуры. Таким образом, this должно быть однозначно назначено (§9.4) в каждом местоположении, где возвращается конструктор. Аналогичным образом, его нельзя читать (даже неявно) в теле конструктора перед определенным назначением.
Если конструктор экземпляра структуры задает инициализатор конструктора, этот инициализатор считается определенным назначением для этого, которое происходит до текста конструктора. Поэтому само тело не имеет требований к инициализации.
Поля экземпляров (отличные от fixed полей) должны быть определенно назначены в конструкторах экземпляров структуры, которые не имеют инициализатора this() .
Пример. Рассмотрим реализацию конструктора экземпляра ниже:
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.24.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.5.10 Статические конструкторы
Статические конструкторы для структур следуют большинству правил, что и для классов. Выполнение статического конструктора для типа структуры активируется первым из следующих событий, происходящих в домене приложения:
- Ссылается на статический член типа структуры.
- Вызывается явно объявленный конструктор типа структуры.
Примечание. Создание значений по умолчанию (§16.5.5) типов структур не активирует статический конструктор. (Примером этого является начальное значение элементов в массиве.) конечная заметка
Свойства 16.5.11
Property_declaration (§15.7.1) для свойства экземпляра в struct_declaration может содержать property_modifierreadonly. Однако статичное свойство не должно содержать модификатор.
Во время компиляции возникает ошибка, если попытаться изменить состояние экземплярной переменной структуры через объявленное в этой структуре свойство только для чтения.
Это ошибка времени компиляции для автоматически реализуемого свойства с модификатором readonly, которое также имеет аксессор set.
Это ошибка на этапе компиляции, если автоматически реализованное свойство в readonly структуре содержит аксессор set.
Автоматически реализованное свойство, объявленное внутри readonly структуры, не обязательно должно иметь readonly модификатор, так как его get аксессор неявно предполагается как только для чтения.
Ошибка времени компиляции возникает, если у свойства имеется модификатор readonly, а также на любом из его аксессоров get и set.
Это ошибка во время компиляции для свойства, который должен иметь модификатор чтения на всех его методах доступа.
Примечание. Чтобы исправить ошибку, переместите модификатор из методов доступа к самому свойству. конечная заметка
Для выражения метода доступа к свойству: s.P
- Это ошибка во время компиляции, если
s.Pвызывает методMдоступа набора типаT, когда процесс в §12.6.6.1 создаст временную копиюs. - При
s.Pвызове методаTдоступа типа выполняется процесс в §12.6.6.1 , включая создание временной копииsпри необходимости.
Автоматически реализованные свойства (§15.7.4) используют скрытые поля резервной копии, которые доступны только для методов доступа к свойствам.
Примечание. Это ограничение доступа означает, что конструкторы в структурах, содержащих автоматически реализованные свойства, часто требуют явного инициализатора конструктора, даже если в остальных случаях он им не нужен. Это необходимо для выполнения требования, чтобы все поля были определённо назначены до вызова любого члена функции или возврата конструктора. конечная заметка
Методы 16.5.12
method_declaration (§15.6.1) для метода экземпляра в struct_declaration может содержать method_modifierreadonly. Однако статический метод не должен содержать этот модификатор.
Это ошибка во время компиляции, которая пытается изменить состояние переменной структуры экземпляра с помощью метода чтения, объявленного в этой структуре.
Хотя метод readonly может вызывать одноуровневый метод, нечитающийся или свойство или индексатор доступа, это приводит к созданию неявной копии this в качестве оборонительной меры.
Метод readonly может вызывать одноуровневое свойство или метод доступа с набором индексатора, который доступен для чтения. Если метод доступа члена-сиблинга не является явно или неявно только для чтения, возникает ошибка компиляции.
Все method_declaration частичного метода должны иметь readonly модификатор, или ни один из них не должен иметь его.
Индексаторы 16.5.13
Indexer_declaration (§15.9) для индексатора экземпляров в struct_declaration может содержать indexer_modifierreadonly.
Это ошибка времени компиляции, возникающая при попытке изменить состояние переменной структуры экземпляра с помощью индексатора с модификатором readonly, объявленного в этой структуре.
Это ошибка компиляции, если использовать модификатор readonly для самого индексатора, а также на любом из его get или set аксессоров.
Индексатор с модификатором readonly во всех своих аксессорах вызывает ошибку времени компиляции.
Примечание. Чтобы исправить ошибку, переместите модификатор из методов доступа к самому индексатору. конечная заметка
16.5.14 События
Объявление события (§15.8.1) для экземпляра, несвязанного с полем, в объявлении структуры может содержать модификатор событияreadonly. Однако статическое событие не должно содержать этот модификатор.
Ограничение безопасного контекста 16.5.15
16.5.15.1 General
Во время компиляции каждое выражение связано с контекстом, в котором можно безопасно получить доступ к этому экземпляру и всем его полям, его безопасный контекст. Безопасный контекст — это контекст, включающий выражение, в который значение может безопасно передаваться.
Любое выражение, тип на этапе компиляции которого не является структурой ссылок, имеет безопасный контекст вызова.
Выражение default любого типа имеет безопасный контекст в вызывающей среде.
Для любого выражения, отличного от стандартного, тип которого на этапе компиляции является ref struct, имеет безопасный контекст, который определяется следующими разделами.
Записи безопасного контекста фиксируют, в какой контекст может быть скопировано значение. Учитывая присваивание из выражения E1 с безопасным контекстом S1, в выражение E2 с безопасным контекстом S2, это будет ошибкой, если S2 окажется более широким контекстом, чем S1.
Существует четыре разных значения безопасного контекста, аналогичные значениям контекста ref-safe-context, определенным для ссылочных переменных (§9.7.2): declaration-block, function-member, return-only и caller-context. Безопасный контекст выражения ограничивает его использование следующим образом:
- Для инструкции
return e1return безопасный контекстe1должен быть по крайней мере возвращаемым. - Для назначения
e1 = e2безопасный контекстe2должен быть по крайней мере таким же широким, как безопасный контекстe1. - Для назначения
outпараметру безопасный контекст правой стороны должен быть по крайней мере возвращаемым.
Безопасный контекст параметра 16.5.15.2
Параметр типа ref struct, включая this параметр метода экземпляра, имеет контекст безопасности контекста вызова.
Параметр out типа структуры ссылок имеет безопасный контекст только возвращаемого значения.
Параметр this в конструкторе структуры имеет безопасный контекст только возвращаемого значения.
Безопасный контекст локальной переменной 16.5.15.3
Локальная переменная структуры типа ref имеет безопасный контекст следующим образом:
- Если переменная является переменной
foreachитерации цикла, то безопасный контекст переменной совпадает с безопасным контекстомforeachвыражения цикла. - В противном случае, если объявление переменной имеет инициализатор, безопасный контекст переменной совпадает с безопасным контекстом этого инициализатора.
- В противном случае переменная в точке объявления является неинициализированной и находится в безопасном контексте контекста вызывающего.
См. статью 9.7.2.1 и §9.7.2.2.
Безопасный контекст поля 16.5.15.4
Ссылка на поле e.F, где тип F является типом структуры ref, имеет контекст безопасности, который совпадает с контекстом безопасности e.
Операторы 16.5.15.5
Приложение определяемого пользователем оператора рассматривается как вызов метода (§16.5.15.6).
Для оператора, который дает значение, например e1 + e2 или c ? e1 : e2, безопасный контекст результата является самым узким контекстом среди безопасных контекстов операндов оператора. В результате для унарного оператора, который дает значение, например +e, безопасный контекст результата является безопасным контекстом операнда.
Примечание. Первый операнд условного оператора — это
bool, поэтому его безопасный контекст — контекст вызывающего. Из этого следует, что результирующий безопасный контекст является самым узким безопасным контекстом второго и третьего операнда. конечная заметка
Метод и вызов свойств 16.5.15.6
Значение, полученное из вызова e1.M(e2, ...) метода или вызова e.Pсвойства, где M() не возвращает ref-to-ref-struct, имеет безопасный контекст наименьшего из следующих контекстов:
- Вызывающий контекст.
- Если возвращается
ref structзначение, безопасный контекст, внесенный всеми выражениями аргументов (включая приемник), исключая аргументы, соответствующиеscopedпараметрам и исключениямoutаргументов. - При возврате возвращается
ref structконтекст ref-safe-context, внесенный всемиrefаргументами, за исключениемscoped refпараметров и исключенияoutаргументов.
Если M() возвращает ref-to-ref-структуру, безопасный контекст совпадает с безопасным контекстом всех аргументов, которые являются ref-to-ref-struct. Это ошибка, если есть несколько таких аргументов с различными безопасными контекстами.
Для целей этих правил заданный аргумент expr , переданный параметру p:
- Если
pэтоscoped refтак, тоexprне вносит ref-safe-context. - Если
pэтоscopedтак,exprто не вносит безопасный контекст. - Если
pэтоoutтак, тоexprне вносит ref-safe-context или safe-context.
Вызов свойства (либо get или set) рассматривается как вызов метода основополагающего метода согласно приведенным выше правилам.
Пример. Ниже показано, как
scopedвлияет на безопасный контекст возвращаемого значения метода:ref struct RS { public ref int RefField; public RS(ref int i) { RefField = ref i; } } class C { static RS CreateAndCapture(ref int value) { // OK: ref-safe-context of `ref value` is caller-context, // safe-context contributed is caller-context. return new RS(ref value); } static RS CreateWithoutCapture(scoped ref int value) { // Error: `value` is scoped ref so it does not contribute // ref-safe-context. The constructor needs ref-safe-context // of caller-context but `value` only has function-member. return new RS(ref value); } }конец примера
Аргументы метода 16.5.15.7 должны соответствовать
Для вызова любого метода e.M(a1, a2, ... aN):
Вычислите самый узкий безопасный контекст из:
- вызывающий контекст.
- Безопасный контекст всех аргументов.
- ref-safe-context всех
refаргументов, соответствующие параметры которых имеют ref-safe-context объекта caller-context.
Все
refаргументыref structтипов должны назначаться значением с помощью этого безопасного контекста. В этом правилеrefне обобщает включениеinиout.
Для вызова любого метода e.M(a1, a2, ... aN):
Вычислите самый узкий безопасный контекст из:
- вызывающий контекст.
- Безопасный контекст всех аргументов.
- Контекст ref-safe-всех
refаргументов, соответствующие параметры которых неscopedявляются.
Все
outаргументыref structтипов должны назначаться значением с помощью этого безопасного контекста.
Наличие scoped позволяет разработчикам уменьшить трение, которое создает это правило путем маркировки параметров, которые не возвращаются как scoped. Это удаляет эти аргументы из (1) в обоих вышеописанных случаях и обеспечивает большую гибкость для вызывающих сторон.
Пример. Ниже показано, как правило method-arguments-must-match предотвращает хранение значения с более узким безопасным контекстом в
refаргументе с более широким безопасным контекстом:ref struct R { } class C { static void F0(ref R a, scoped ref R b) { } static void F1(ref R x, scoped R y) { // Error: The narrowest safe-context is function-member (from `y`) // but `x` is a ref argument of a ref struct type whose // safe-context is caller-context. It must be assignable by // a value with that narrowest safe-context, which fails. F0(ref x, ref y); } }конец примера
16.5.15.8 Вывод безопасного контекста выражений объявления
Безопасный контекст переменной объявления из out аргумента () или деконструкции (M(x, out var y)(var x, var y) = M()) является самым узким из следующих:
- вызывающий контекст.
- Если переменная вне помечена
scoped, то блок объявления (т. е. член-функция или более узкий). - Если тип переменной out является типом
ref struct, рассмотрите все аргументы для содержащего вызова, включая приемника:- Безопасный контекст любого аргумента, где соответствующий параметр не
outявляется и имеет безопасный контекст возвращаемого значения или более широкий. - Контекст ref-safe-any аргумента, где соответствующий параметр имеет ref-safe-context только возвращаемого или более широкого.
- Безопасный контекст любого аргумента, где соответствующий параметр не
Пример. Ниже показано, как безопасный контекст переменной
outобъявления выводится из других аргументов в вызов:ref struct RS { public RS(ref int x) { } static void M0(RS input, out RS output) => output = input; static RS M1() { var i = 0; var rs1 = new RS(ref i); // safe-context of rs1 is function-member M0(rs1, out var rs2); // safe-context of rs2 is function-member return rs2; // Error: rs2 cannot escape function-member } static void M2(RS rs1) { M0(rs1, out scoped var rs2); // scoped forces safe-context to // declaration-block } }В
M1случае безопасного контекстаrs2является самым узким из вызывающего контекста и безопасного контекстаrs1(член функции), который является членом-функцией. Поэтомуrs2не может быть возвращено. ВM2модификатореscopedпринудительно применяется безопасныйrs2контекст для блокировки объявления.конец примера
Безопасный контекст инициализатора объектов 16.5.15.9
Безопасный контекст выражения инициализатора объектов является самым узким из следующих:
- Безопасный контекст вызова конструктора.
- Безопасный контекст и ref-safe-context аргументов для индексаторов инициализатора элементов, которые могут избежать выхода из приемника.
- Безопасный контекст RHS назначений в инициализаторах членов для нечитаемых наборов или ref-safe-context в случае назначения ссылок.
Примечание. Другой способ моделирования заключается в рассмотрении любого аргумента инициализатора элемента, который может быть назначен получателю как аргумент конструктору. конечная заметка
Пример. Ниже показано, как инициализатор объектов сужает безопасный контекст результирующего значения:
using System; ref struct S { public Span<int> Field; public S(ref int i) { } } class C { static S Example() { Span<int> stackSpan = stackalloc int[42]; int i = 0; // safe-context is narrowest of: // constructor safe-context (caller-context) and // RHS of Field assignment (stackSpan: function-member) // = function-member var x = new S(ref i) { Field = stackSpan }; return x; // Error: x has safe-context of function-member } }конец примера
16.5.15.10 stackalloc
Результат выражения stackalloc имеет безопасный контекст элемента-функции.
Вызовы конструктора 16.5.15.11
new Выражение, вызывающее конструктор, подчиняется тем же правилам, что и вызов метода, возвращающего создаваемый тип.
Кроме того, безопасный контекст является наименьшим из безопасных контекстов всех аргументов и операндов всех выражений инициализатора объектов, если рекурсивно присутствует какой-либо инициализатор. Дополнительные сведения см. в разделе "16.5.15.9 ".
ECMA C# draft specification