diff --git a/standard/basic-concepts.md b/standard/basic-concepts.md index 8a3e84c1f..0ed464663 100644 --- a/standard/basic-concepts.md +++ b/standard/basic-concepts.md @@ -778,12 +778,12 @@ The ***scope*** of a name is the region of program text within which it is possi - The scope of a name defined or imported by a *using_directive* ([§14.5](namespaces.md#145-using-directives)) extends over the *global_attributes*, *statement_list*s, and *namespace_member_declaration*s of the *compilation_unit* or *namespace_body* in which the *using_directive* occurs. A *using_directive* may make zero or more namespace or type names available within a particular *compilation_unit* or *namespace_body*, but does not contribute any new members to the underlying declaration space. In other words, a *using_directive* is not transitive but rather affects only the *compilation_unit* or *namespace_body* in which it occurs. - The scope of a type parameter declared by a *type_parameter_list* on a *class_declaration* ([§15.2](classes.md#152-class-declarations)) is the *class_base*, *type_parameter_constraints_clause*s, and *class_body* of that *class_declaration*. > *Note*: Unlike members of a class, this scope does not extend to derived classes. *end note* -- The scope of a type parameter declared by a *type_parameter_list* on a *struct_declaration* ([§16.2](structs.md#162-struct-declarations)) is the *struct_interfaces*, *type_parameter_constraints_clause*s, and *struct_body* of that *struct_declaration*. +- The scope of a type parameter declared by a *type_parameter_list* on a *struct_declaration* ([§16.2](structs.md#162-struct-declarations)) is the *struct_interfaces*, *type_parameter_constraints_clause*s, and *struct_body* (or *record_struct_body*) of that *struct_declaration*. - The scope of a type parameter declared by a *type_parameter_list* on an *interface_declaration* ([§19.2](interfaces.md#192-interface-declarations)) is the *interface_base*, *type_parameter_constraints_clause*s, and *interface_body* of that *interface_declaration*. - The scope of a type parameter declared by a *type_parameter_list* on a *delegate_declaration* ([§21.2](delegates.md#212-delegate-declarations)) is the *return_type*, *parameter_list*, and *type_parameter_constraints_clause*s of that *delegate_declaration*. - The scope of a type parameter declared by a *type_parameter_list* on a *method_declaration* ([§15.6.1](classes.md#1561-general)) is the *method_declaration*. - The scope of a member declared by a *class_member_declaration* ([§15.3.1](classes.md#1531-general)) is the *class_body* in which the declaration occurs. In addition, the scope of a class member extends to the *class_body* of those derived classes that are included in the accessibility domain ([§7.5.3](basic-concepts.md#753-accessibility-domains)) of the member. -- The scope of a member declared by a *struct_member_declaration* ([§16.3](structs.md#163-struct-members)) is the *struct_body* in which the declaration occurs. +- The scope of a member declared by a *struct_member_declaration* ([§16.3](structs.md#163-struct-members)) is the *struct_body* (or *record_struct_body*) in which the declaration occurs. - The scope of a member declared by an *enum_member_declaration* ([§20.4](enums.md#204-enum-members)) is the *enum_body* in which the declaration occurs. - The scope of a parameter declared in a *method_declaration* ([§15.6](classes.md#156-methods)) is the *method_body* or *ref_method_body* of that *method_declaration*. diff --git a/standard/classes.md b/standard/classes.md index 3e89056da..681099ceb 100644 --- a/standard/classes.md +++ b/standard/classes.md @@ -21,7 +21,7 @@ class_declaration class_tag : 'class' - | 'record' + | 'record' 'class'? ; ``` @@ -31,7 +31,7 @@ A class declaration shall not supply *type_parameter_constraints_clause*s unless A class declaration that supplies a *type_parameter_list* is a generic class declaration. Additionally, any class nested inside a generic class declaration or a generic struct declaration is itself a generic class declaration, since type arguments for the containing type shall be supplied to create a constructed type ([§8.4](types.md#84-constructed-types)). -If *class_tag* contains `record`, that class is a ***record class***; otherwise, it is a ***non-record class***. +If *class_tag* contains `record`, that class is a ***record class***; otherwise, it is a ***non-record class***. `record` and `record class` are equivalent. For a record class, *class_modifier* shall not be `static`. @@ -6465,7 +6465,9 @@ Expression variables declared in *argument_list* are in scope within the *argume #### 15.16.5.3 Properties -For each parameter of a positional *record_declaration* ([§15.2.1](classes.md#1521-general)) there shall be a corresponding public property member whose name and type are taken from the value parameter declaration. +For each parameter of a positional *record_declaration* ([§15.2.1](classes.md#1521-general)) that has the same name and type as an inherited or explicitly declared instance field, the remainder of this subclause does not apply. + +For each parameter of a positional *record_declaration* there shall be a corresponding public property member whose name and type are taken from the value parameter declaration. For a record class: @@ -6520,6 +6522,3 @@ A positional record class ([§15.2.1](classes.md#1521-general)) with at least on > ``` > > *end example* - - - diff --git a/standard/expressions.md b/standard/expressions.md index eb6257f03..6187818ab 100644 --- a/standard/expressions.md +++ b/standard/expressions.md @@ -3902,7 +3902,7 @@ An awaiter’s implementation of the interface methods `INotifyCompletion.OnComp ## 12.10 With expressions -A *with_expression* allows for ***non-destructive mutation*** by making a new record class instance that is a copy of an existing record class instance, optionally with specified properties and fields modified. +A *with_expression* allows for ***non-destructive mutation*** by making a new record class, record struct, or non-record struct instance that is a copy of an existing record class, record struct, or non-record struct instance, respectively, optionally with specified properties and fields modified. ```ANTLR with_expression @@ -3913,7 +3913,7 @@ with_expression A *with_expression* is not permitted as a statement. -The receiver type shall be non-`void` and of some record class type. +The receiver type shall be non-`void` and of some record class, record struct, or non-record struct type. *identifier* shall be an accessible instance field or property of the receiver's type. @@ -3921,7 +3921,8 @@ All non-positional properties being changed shall have both set and init accesso This expression is evaluated as follows: -- The receiver's clone method ([§15.16.3](classes.md#15163-copy-and-clone-members)) is invoked, and its result is converted to the receiver’s type. +- For a record class type, the receiver's clone method ([§15.16.3](classes.md#15163-copy-and-clone-members)) is invoked, and its result is converted to the receiver’s type. +- For a record struct or non-record struct type, the receiver is copied. - Each `member_initializer` is processed the same way as an assignment to a field or property access of the result of the conversion. Assignments are processed in lexical order. If *member_initializer_list* is omitted, no members are changed. diff --git a/standard/structs.md b/standard/structs.md index 517c6b6fe..c428a6f3f 100644 --- a/standard/structs.md +++ b/standard/structs.md @@ -16,19 +16,37 @@ A *struct_declaration* is a *type_declaration* ([§14.7](namespaces.md#147-type- ```ANTLR 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 ';'? + | ';' + ; ``` -A *struct_declaration* consists of an optional set of *attributes* ([§23](attributes.md#23-attributes)), followed by an optional set of *struct_modifier*s ([§16.2.2](structs.md#1622-struct-modifiers)), followed by an optional `ref` modifier ([§16.2.3](structs.md#1623-ref-modifier)), followed by an optional partial modifier ([§15.2.7](classes.md#1527-partial-type-declarations)), followed by the keyword `struct` and an *identifier* that names the struct, followed by an optional *type_parameter_list* specification ([§15.2.3](classes.md#1523-type-parameters)), followed by an optional *struct_interfaces* specification ([§16.2.5](structs.md#1625-struct-interfaces)), followed by an optional *type_parameter_constraints-clauses* specification ([§15.2.5](classes.md#1525-type-parameter-constraints)), followed by a *struct_body* ([§16.2.6](structs.md#1626-struct-body)), optionally followed by a semicolon. +There are two kinds of struct: ***non-record struct***, as declared by *non_record_struct_declaration*, and ***record struct***, as declared by *record_struct_declaration*. A non-record struct is the kind of struct that C# has supported since the language’s inception. Record structs were added much later and are discussed in §record-structs. The differences between the two kinds are discussed in §record-struct-diffs. + +A *non_record_struct_declaration* consists of an optional set of *attributes* ([§23](attributes.md#23-attributes)), followed by an optional set of *struct_modifier*s ([§16.2.2](structs.md#1622-struct-modifiers)), followed by an optional `ref` modifier ([§16.2.3](structs.md#1623-ref-modifier)), followed by an optional partial modifier ([§15.2.7](classes.md#1527-partial-type-declarations)), followed by the keyword `struct` and an *identifier* that names the struct, followed by an optional *type_parameter_list* specification ([§15.2.3](classes.md#1523-type-parameters)), followed by an optional *struct_interfaces* specification ([§16.2.5](structs.md#1625-struct-interfaces)), followed by an optional *type_parameter_constraints-clauses* specification ([§15.2.5](classes.md#1525-type-parameter-constraints)), followed by a *struct_body* ([§16.2.6](structs.md#1626-struct-body)), optionally followed by a semicolon. -A struct declaration shall not supply *type_parameter_constraints_clause*s unless it also supplies a *type_parameter_list*. +A *struct_declaration* shall not supply *type_parameter_constraints_clause*s unless it also supplies a *type_parameter_list*. -A struct declaration that supplies a *type_parameter_list* is a generic struct declaration. Additionally, any struct nested inside a generic class declaration or a generic struct declaration is itself a generic struct declaration, since type arguments for the containing type shall be supplied to create a constructed type ([§8.4](types.md#84-constructed-types)). +A *struct_declaration* that supplies a *type_parameter_list* is a generic struct declaration. Additionally, any struct nested inside a generic class declaration or a generic struct declaration is itself a generic struct declaration, since type arguments for the containing type shall be supplied to create a constructed type ([§8.4](types.md#84-constructed-types)). -A struct declaration that includes a `ref` keyword shall not have a *struct_interfaces* part. +A *non_record_struct_declaration* that includes a `ref` modifier shall not have a *struct_interfaces* part. ### 16.2.2 Struct modifiers @@ -63,7 +81,7 @@ When an instance of a readonly struct is passed to a method, its `this` is treat ### 16.2.3 Ref modifier -The `ref` modifier indicates that the *struct_declaration* declares a type whose instances are allocated on the execution stack. These types are called ***ref struct*** types. The `ref` modifier declares that instances may contain ref-like fields, and shall not be copied out of its safe-context ([§16.4.15](structs.md#16415-safe-context-constraint)). The rules for determining the safe context of a ref struct are described in [§16.4.15](structs.md#16415-safe-context-constraint). +The `ref` modifier indicates that the *non_record_struct_declaration* declares a type whose instances are allocated on the execution stack. These types are called ***ref struct*** types. The `ref` modifier declares that instances may contain ref-like fields, and shall not be copied out of its safe-context ([§16.4.15](structs.md#16415-safe-context-constraint)). The rules for determining the safe context of a ref struct are described in [§16.4.15](structs.md#16415-safe-context-constraint). It is a compile-time error if a ref struct type is used in any of the following contexts: @@ -182,6 +200,299 @@ An instance member definition or accessor of an instance property, indexer, or e > > The `readonly` method `AddMessage` can change the state of a message list. The `InitializeMessages` member can clear and re-initialize the list of messages. In the case of `AddMessage`, the `readonly` modifier is valid. In the case of `InitializeMessages`, adding the `readonly` modifier is invalid. *end example* +## §record-structs Record structs + +### §record-structs-general General + +A record struct is a specialized value type that is optimized for storing data rather than behavior. It provides built-in functionality that would normally require significant “boilerplate” code in a non-record struct, such as value-based equality and easy immutability. + +```ANTLR +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 + ; +``` + +A *record_struct_declaration* consists of an optional set of *attributes* ([§23](attributes.md#23-attributes)), followed by an optional set of *struct_modifier*s ([§16.2.2](structs.md#1622-struct-modifiers)), followed by an optional partial modifier ([§15.2.7](classes.md#1527-partial-type-declarations)), followed by the keyword `record`, followed by the keyword `struct` and an *identifier* that names the struct, followed by an optional *type_parameter_list* specification ([§15.2.3](classes.md#1523-type-parameters)), followed by an optional *delimited_parameter_list* specification ([§15.6.2.1](classes.md#15621-general)), followed by an optional *struct_interfaces* specification ([§16.2.5](structs.md#1625-struct-interfaces)), followed by an optional *type_parameter_constraints-clauses* specification ([§15.2.5](classes.md#1525-type-parameter-constraints)), followed by a *record_struct_body*. + +A *record_struct_declaration* having a *delimited_parameter_list* declares a ***positional record struct***. + +At most only one *record_struct_declaration* containing `partial` may provide a *delimited_parameter_list*. + +The parameters in *delimited_parameter_list* shall not have `ref`, `out` or `this` modifiers; however, `in` and `params` modifiers are permitted. + +### §record-structs-members Struct members + +For a record struct, as well as those members identified by [§16.3](structs.md#163-struct-members), the member set includes the members implicitly provided by the implementation (§implicit-members). + +It is an error for a member of a record struct to be named `Clone`. + +It is an error for an instance field of a record struct to have an unsafe type. + +### §record-structs-body Record struct body + +The *record_struct_body* of a record struct identifies the explicitly declared members of that struct. + +```ANTLR +record_struct_body + : struct_body ';'? + | ';' + ; +``` + +For a *record_struct_declaration*, the *record_struct_body*s `{}`, `{};`, and `;` are equivalent. They all indicate that the only members are those implicitly provided by the implementation (§implicit-members). + +### §implicit-members Implicit record struct members + +#### §implicit-members-general General + +In the case of a record struct, members are provided by the implemenation unless a member with a “matching” signature is declared in the *record_struct_body* or an accessible concrete non-virtual member with a “matching” signature is inherited. A matching member prevents the implementation from providing that member only, not any other provided members. Two members are considered matching if they have the same signature or would be considered “hiding” in an inheritance scenario. (See Signatures and overloading [§7.6](basic-concepts.md#76-signatures-and-overloading).) + +The members provided by the implementation are described in the following subclauses. + +#### §prim-constr Primary constructors + +As with a non-record class, a non-record struct with a *delimited_parameter_list* has a primary constructor ([§xx](classes.md#xx)) provided by the implementation. The semantics of the non-record class version apply here as well and are augmented by the text in this subclause. + +In the case of a non-record class, the implementation shall provide a private, init-only field for each parameter. However, for a non-record struct, the storage is read-write and provided in some unspecified manner. + +Instance field declarations for a non-record struct are permitted to include variable initializers. If there is no primary constructor, the instance initializers execute as part of the parameterless constructor. Otherwise, at runtime the primary constructor executes the instance initializers appearing in the *struct_body*. + +#### §rec-struct-equalmem Equality members + +The provided equality members are similar to those for a record class ([§15.16.2](classes.md#15162-equality-members)), except for the lack of method `EqualityContract`, null checks, or inheritance. + +A record struct `R` implements `System.IEquatable` and includes a synthesized strongly-typed overload of `Equals(R other)`, which is public, as follows: + +```csharp +public readonly bool Equals(R other); +``` + +This method can be declared explicitly. However, it is an error if the explicit declaration does not match the expected signature or accessibility. + +If `Equals(R other)` is user-defined but `GetHashCode` is not, a warning shall be produced. + +The provided `Equals(R)` shall return `true` if and only if for each instance field `fieldN` in the record struct the value of `System.Collections.Generic.EqualityComparer.Default.Equals(fieldN, other.fieldN)`, where `TN` is the field type, is `true`. + +The record struct includes provided `==` and `!=` operators equivalent to operators declared as follows: + +```csharp +public static bool operator==(R r1, R r2) => r1.Equals(r2); +public static bool operator!=(R r1, R r2) => !(r1 == r2); +``` + +The `Equals` method called by the `==` operator is the `Equals(R other)` method specified above. The `!=` operator delegates to the `==` operator. It is an error if the operators are declared explicitly. + +The record struct includes a provided override method declared as follows: + +```csharp +public override readonly bool Equals(object? obj); +``` + +It is an error if the override is declared explicitly. The provided override shall return `other is R temp && Equals(temp)` where `R` is the record struct. + +The record struct includes a provided override method declared as follows: + +```csharp +public override readonly int GetHashCode(); +``` + +This method may be declared explicitly. + +A warning shall be reported if one of `Equals(R)` and `GetHashCode()` is explicitly declared but the other method is not. + +The provided override of `GetHashCode()` shall return an `int` result of combining the values of `System.Collections.Generic.EqualityComparer.Default.GetHashCode(fieldN)` for each instance field `fieldN` with `TN` being the type of `fieldN`. + +> *Example*: Consider the following record struct: +> +> +> ```csharp +> record struct R1(T1 P1, T2 P2); +> ``` +> +> For this, the provided equality members would be something like: +> +> +> ```csharp +> struct R1 : IEquatable +> { +> 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.Default.Equals(P1, other.P1) && +> EqualityComparer.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.Default.GetHashCode(P1), +> EqualityComparer.Default.GetHashCode(P2)); +> } +> } +> ``` +> +> *end example* + +#### §rec-struct-prtmem Printing members + +A record struct includes a provided method, declared as follows: + +```csharp +private bool PrintMembers(System.Text.StringBuilder builder); +``` + +This method performs the following tasks: + +1. For each of the record struct’s printable members (non-static public field and readable property members), appends that member's name followed by “` = `“ followed by the member's value separated with “`, “`, +2. Returns true if the record struct has printable members. + +For a member that has a value type, its value shall be converted to a string representation. + +If the record’s printable members do not include a readable property with a non-`readonly` `get` accessor, then the synthesized `PrintMembers` is `readonly`. There is no requirement for the record’s fields to be `readonly` for the `PrintMembers` method to be `readonly`. + +The `PrintMembers` method can be declared explicitly. However, it is an error if the explicit declaration does not match the expected signature or accessibility. + +The record struct includes a provided method, declared as follows: + +```csharp +public override string ToString(); +``` + +If the record struct’s `PrintMembers` method is `readonly`, then the provided `ToString()` method shall be `readonly`. + +This method can be declared explicitly. It is an error if the explicit declaration does not match the expected signature or accessibility. + +This method performs the following tasks: + +1. Creates a `StringBuilder` instance, +2. Appends the record struct name to the builder, followed by “` { `“, +3. Invokes the record struct's `PrintMembers` method giving it the builder, followed by “` `” if it returned true, +4. Appends “`}`”, +5. Returns the builder’s contents with `builder.ToString()`. + +> *Example*: Consider the following record struct: +> +> +> ```csharp +> record struct R1(T1 P1, T2 P2); +> ``` +> +> For this record struct, the provided printing members would be something like: +> +> +> +> ```csharp +> struct R1 : IEquatable +> { +> 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(); +> } +> } +> ``` +> +> *end example* + +#### §rec-struct-pos-mem Positional record struct members + +##### §rec-struct-pos-mem-gen General + +As well as providing the members described in the preceding subclauses, positional record structs ([§16.2.1](structs.md#1621-general)) result in the implementation providing additional members with the same conditions as the other provided members, as described in the following subclauses. + +##### §rec-struct-pos-mem-pricon Primary constructor + +As with a record class, a record struct with a *delimited_parameter_list* has a primary constructor ([§xx](classes.md#xx)) provided by the implementation. The semantics of the record class version apply here as well and are augmented by the text in this subclause. + +In the case of a record class, the implementation shall provide a private, init-only field for each parameter. However, for a record struct, the storage is read-write and provided in some unspecified manner. + +Instance field declarations for a record struct are permitted to include variable initializers. If there is no primary constructor, the instance initializers execute as part of the parameterless constructor. Otherwise, at runtime the primary constructor executes the instance initializers appearing in the *record_struct_body*. + +The definite assignment rules for struct instance constructors apply to the primary constructor of record structs. For instance, the following is an error: + +> +> ```csharp +> record struct Pos(int X) // def assignment error in primary constructor +> { +> private int x; +> public int X { +> get { return x; } set { x = value; } +> } = X; +> } +> ``` + +##### §rec-struct-pos-mem-props Properties + +For each parameter of a *delimited_parameter_list* that has the same name and type as an explicitly declared instance field, the remainder of this subclause does not apply. + +For each record struct parameter of a *delimited_parameter_list* there is provided a corresponding public property member whose name and type are taken from the value parameter declaration. + +For a record struct: + +- a public `get` and `init` auto-property is created if the record struct has a `readonly` modifier, `get` and `set` otherwise. Both kinds of set accessors (`set` and `init`) are considered “matching.” So, the user may declare an init-only property in place of a provided mutable one. + +- An inherited `abstract` property with matching type is overridden. + +- No auto-property is created if the record struct has an instance field with expected name and type. + +- It is an error if the inherited property does not have `public` `get` and `set`/`init` accessors. + +- It is an error if the inherited property or field is hidden. + +- The auto-property is initialized to the value of the corresponding primary constructor parameter. + +- Attributes may be applied to the provided auto-property and its backing field by using `property:` or `field:` targets, respectively, for attributes syntactically applied to the corresponding record struct parameter. + +##### §rec-struct-pos-mem-decon Deconstruct + +A positional record struct with at least one parameter provides a public `void`-returning instance method called `Deconstruct` with an out parameter declaration for each parameter of the primary constructor declaration. Each parameter of `Deconstruct` has the same type as the corresponding parameter of the primary +constructor declaration. The body of the method assigns each parameter of the Deconstruct method to the value from an instance member access to a member of the same name. +If the instance members accessed in the body do not include a property with a non-`readonly` `get` accessor, then the synthesized `Deconstruct` method is `readonly`. +The method can be declared explicitly. It is an error if the explicit declaration does not match the expected signature or accessibility, or is static. + +## §record-struct-diffs Record struct and non-record struct differences + +A record struct differs from a non-record struct in several important ways: + +- It is declared using the keywords `record struct` instead of just `struct`. +- It has a number of members provided for it by the implementation. +- Its declaration may contain a *delimited_parameter_list* having zero or more parameters, which results in the provision of a primary constructor having those parameters. For each parameter, the implementation shall provide field-like storage and a property with get and init accessor. A `Deconstruct` method is also provided. +- Its record struct body may be omitted. +- It shall not have a member called `Clone`. +- It shall not have an instance field with an unsafe type. + ## 16.4 Class and struct differences ### 16.4.1 General