Структуры записей
Заметка
Эта статья является спецификацией компонентов. Спецификация служит проектным документом для функции. Она включает предлагаемые изменения спецификации, а также информацию, необходимую на протяжении проектирования и разработки функционала. Эти статьи публикуются до тех пор, пока предложенные изменения спецификации не будут завершены и включены в текущую спецификацию ECMA.
Может возникнуть некоторое несоответствие между спецификацией компонентов и завершенной реализацией. Эти различия зафиксированы в соответствующих собраниях по проектированию языка (LDM).
Дополнительные сведения о процессе внедрения спецификаций функций в стандарт языка C# см. в статье о спецификациях .
Проблема чемпиона: https://github.com/dotnet/csharplang/issues/4334
Синтаксис структуры записи выглядит следующим образом:
record_struct_declaration
: attributes? struct_modifier* 'partial'? 'record' 'struct' identifier type_parameter_list?
parameter_list? struct_interfaces? type_parameter_constraints_clause* record_struct_body
;
record_struct_body
: struct_body
| ';'
;
Типы структур записей — это типы значений, такие как другие типы структур. Они неявно наследуются от класса System.ValueType
.
Модификаторы и члены структуры записей подвергаются тем же ограничениям, что и структуры (специальные возможности для типов, модификаторов элементов, base(...)
инициализаторов экземпляров, определенное назначение для this
в конструкторе, деструкторах, ...). Структуры записей также будут следовать тем же правилам, что и структуры для конструкторов экземпляров без параметров и инициализаторов полей, но в этом документе предполагается, что эти ограничения будут отменены для структур, как правило.
См. раздел §16.4.9 и спецификацию конструкторов структуры без параметров.
Структуры записей не могут использовать модификатор ref
.
По крайней мере одно частичное объявление типа структуры частичной записи может предоставить parameter_list
.
parameter_list
может быть пустым.
Параметры структуры записей не могут использовать модификаторы ref
, out
или this
(но разрешены in
и params
).
Члены структуры записи
Помимо элементов, объявленных в тексте структуры записи, тип структуры записи содержит дополнительные синтезированные элементы. Члены синтезируются, если член с совпадающей подписью не объявлен в теле структуры записи или если унаследован доступный конкретный не виртуальный член с совпадающей подписью. Два члена считаются совпадающими, если у них одинаковая подпись или они рассматриваются как "сокрытие" в сценарии наследования. См. раздел "Подписи и перегрузка" §7.6. Ошибка, если у члена структуры записи имя "Клон".
Наличие небезопасного типа для поля экземпляра структуры записи является ошибкой.
Структурам записи не разрешается объявлять деструкторы.
Синтезированные члены приведены следующим образом:
Участники равноправия
Синтезированные члены равенства похожи на класс записей (Equals
для этого типа, Equals
для типа object
, ==
и !=
операторы для этого типа).
за исключением отсутствия EqualityContract
, проверок null или наследования.
Структуру записей реализует System.IEquatable<R>
и включает синтезированную строго типизированную перегрузку Equals(R other)
, где R
является структурой записей.
Метод public
.
Метод можно объявить явным образом. Это ошибка, если явное объявление не соответствует ожидаемой сигнатуре или уровню доступности.
Если Equals(R other)
определяется пользователем (не синтезируется), но GetHashCode
нет, создается предупреждение.
public readonly bool Equals(R other);
Синтезированный 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 Combine(
EqualityComparer<T1>.Default.GetHashCode(P1),
EqualityComparer<T2>.Default.GetHashCode(P2));
}
}
Элементы печати: методы PrintMembers и ToString
Структуру записи включает синтезированный метод, эквивалентный методу, объявленному следующим образом:
private bool PrintMembers(System.Text.StringBuilder builder);
Метод выполняет следующее:
- для каждого из печатаемых членов структуры записи (нестатическое публичное поле и члены доступных для чтения свойств), добавляет имя этого члена, за которым следует " = " и значение члена, разделенное ", ".
- возвращает true, если у структуры записи есть отображаемые элементы.
Для элемента, имеющего тип значения, мы преобразуем его значение в строковое представление с помощью наиболее эффективного метода, доступного для целевой платформы. В настоящее время это означает вызов ToString
перед передачей в StringBuilder.Append
.
Если в элементах записей, доступных для печати, отсутствует свойство, читаемое с методом доступа, отличным отreadonly
get
, то синтезированный PrintMembers
— это readonly
. Нет требования, чтобы поля записи были readonly
для метода PrintMembers
, чтобы быть readonly
.
Метод 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();
}
}
Элементы структуры позиционной записи
Помимо указанных выше элементов, структуры записей со списком параметров ("позиционные записи") синтезируют дополнительные элементы с теми же условиями, что и члены выше.
Основной конструктор
У структуры записи есть открытый конструктор, подпись которого соответствует параметрам значения объявления типа. Это называется первичным конструктором для типа. Это ошибка, если в структуре присутствуют главный конструктор и конструктор с той же сигнатурой. Если объявление типа не содержит список параметров, основной конструктор не создается.
record struct R1
{
public R1() { } // ok
}
record struct R2()
{
public R2() { } // error: 'R2' already defines constructor with same parameter types
}
Объявления полей экземпляра для структуры записей могут включать инициализаторы переменных. Если основной конструктор отсутствует, инициализаторы экземпляров выполняются как часть конструктора без параметров. В противном случае во время выполнения основной конструктор выполняет инициализаторы экземпляров, отображаемые в тексте записи-структуры.
Если у структуры записи есть основной конструктор, любой определяемый пользователем конструктор должен иметь явный this
инициализатор конструктора, который вызывает первичный конструктор или явно объявленный конструктор.
Параметры основного конструктора, а также члены структуры записи являются доступными в инициализаторах полей или свойств экземпляра. Использование членов экземпляра будет ошибкой в этих местах (аналогично тому, как члены экземпляра доступны в области видимости обычных инициализаторов конструкторов, но их использование вызывает ошибку), но параметры основного конструктора будут доступны и использоваться, и будут затенять члены. Статические члены также могут использоваться.
Предупреждение создается, если параметр первичного конструктора не считывается.
Определенные правила назначения для конструкторов экземпляров структуры применяются к основному конструктору структур записей. Например, следующая ошибка:
record struct Pos(int X) // definite assignment error in primary constructor
{
private int x;
public int X { get { return x; } set { x = value; } } = X;
}
Свойства
Для каждого параметра структуры записи объявления структуры записи существует соответствующий элемент общедоступного свойства, имя и тип которого взяты из объявления параметра значения.
Для структуры записи:
- Создается общедоступное авто-свойство
get
иinit
, если структура записи имеет модификаторreadonly
; в противном случае, создаютсяget
иset
. Оба типа сет аксессоров (set
иinit
) считаются идентичными. Пользователь может объявить свойство, которое устанавливается только при инициализации, вместо автоматически сгенерированного изменяемого свойства. Наследуемое свойствоabstract
с соответствующим типом переопределяется. Автоматическое свойство не создается, если у структуры записи есть поле экземпляра с ожидаемым именем и типом. Это ошибка, если унаследованное свойство не имеетpublic
get
иset
/init
методы доступа. Это ошибка, если унаследованное свойство или поле скрыто.
Автоматическое свойство инициализируется значением соответствующего основного параметра конструктора. Атрибуты можно применять к синтезируемому автоматическому свойству и его резервному полю, используя целиproperty:
илиfield:
для атрибутов, синтаксически применяемых к соответствующему параметру структуры записи.
Деконструкция
Структура позиционной записи с как минимум одним параметром синтезирует экземплярный метод с открытым доступом, возвращающий void, с именем Deconstruct
и объявлением выходного параметра для каждого параметра основного конструктора. Каждый параметр метода Deconstruct имеет тот же тип, что и соответствующий параметр объявления первичного конструктора. Текст метода назначает каждому параметру метода Деконструкция значению из доступа члена экземпляра к элементу того же имени.
Если члены экземпляра, доступ к которым осуществляется в теле, не включают свойство с методом доступаreadonly
get
, то синтезированный метод Deconstruct
является readonly
.
Метод можно объявить явным образом. Это ошибка, если явное объявление не соответствует ожидаемой сигнатуре или доступности, либо если оно статическое.
Разрешить выражение with
для структур
Теперь допускается, чтобы получатель в выражении with
имел тип структуры.
Справа от выражения with
находится member_initializer_list
, содержащая последовательность присваиваний идентификатору , который должен быть доступным полем экземпляра или свойством типа получателя.
Для приемника с типом структуры приемник сначала копируется, затем каждый member_initializer
обрабатывается так же, как присвоение полю или доступ к свойству результата преобразования.
Назначения обрабатываются в лексическом порядке.
Улучшения записей
Разрешить record class
Существующий синтаксис для типов записей позволяет record class
в том же значении, что и record
:
record_declaration
: attributes? class_modifier* 'partial'? 'record' 'class'? identifier type_parameter_list?
parameter_list? record_base? type_parameter_constraints_clause* record_body
;
Разрешить определяемым пользователем позиционным элементам быть полями
Автоматическое свойство не создается, если в записи имеется или наследуется поле экземпляра с ожидаемым именем и типом.
Разрешить конструкторы без параметров и инициализаторы членов в структурах
См. спецификацию конструкторов структуры без параметров.
Открытые вопросы
- как распознать структуры записей в метаданных? (У нас нет невыразимого метода клонирования для использования...)
Отвечено
- убедитесь, что мы хотим сохранить дизайн PrintMembers (отдельный метод, возвращающий
bool
) (ответ: да) - подтвердите, что мы не разрешим
record ref struct
(проблема с полямиIEquatable<RefStruct>
и полями ссылок) (ответ: да) - подтвердите реализацию членов равенства. Альтернатива заключается в том, что синтезированные
bool Equals(R other)
,bool Equals(object? other)
и операторы все просто делегируют кValueType.Equals
. (ответ: да) - подтвердите, что мы хотим разрешить инициализаторы полей при наличии главного конструктора. Хотим ли мы также разрешить конструкторы структур без параметров, раз уж мы этим заняты (проблема с "Activator," по-видимому, устранена)? (ответ: да, обновленная спецификация должна быть проверена в LDM)
- сколько мы хотим сказать о методе
Combine
? (ответ: как можно меньше) - следует ли запретить определяемый пользователем конструктор с сигнатурой конструктора копирования? (ответ: нет, нет понятия конструктора копирования в спецификации структуры записей)
- Подтвердите, что мы хотим запретить членам с именем «Клон». (ответ: правильно)
- дважды проверьте, что синтезированная
Equals
логика функционально эквивалентна реализации среды выполнения (например, float. NaN) (ответ: подтверждено в LDM) - могут ли атрибуты полей или свойств быть помещены в список позиционных параметров? (ответ: да, то же самое, что и для класса записи)
-
with
обобщенных типов? (ответ: не входит в круг задач для C# 10) - должен ли
GetHashCode
включать хэш самого типа, чтобы получить разные значения междуrecord struct S1;
иrecord struct S2;
? (ответ: нет)
C# feature specifications