diff --git a/standard/attributes.md b/standard/attributes.md index b3cd07a51..fb68f81b2 100644 --- a/standard/attributes.md +++ b/standard/attributes.md @@ -105,9 +105,9 @@ class X : Attribute { ... } ### 23.2.3 Positional and named parameters -Attribute classes can have ***positional parameter***s and ***named parameter***s. Each public instance constructor for an attribute class defines a valid sequence of positional parameters for that attribute class. Each non-static public read-write field and property for an attribute class defines a named parameter for the attribute class. For a property to define a named parameter, that property shall have both a public get accessor and a public set accessor. +Attribute classes can have ***positional parameter***s and ***named parameter***s. Each public instance constructor for an attribute class defines a valid sequence of positional parameters for that attribute class. Each non-static public read-write field and non-static public read-write or read-init property for an attribute class defines a named parameter for the attribute class. For a property to define a named parameter, that property shall have both a public get accessor and a public set accessor. -> *Example*: The following example defines an attribute class named `HelpAttribute` that has one positional parameter, `url`, and one named parameter, `Topic`. Although it is non-static and public, the property `Url` does not define a named parameter, since it is not read-write. Two uses of this attribute are also shown: +> *Example*: The following example defines an attribute class named `HelpAttribute` that has one positional parameter, `url`, and one named parameter, `Topic`. Although it is non-static and public, the property `Url` does not define a named parameter, since it is not read-write or read-init. Two uses of this attribute are also shown: > > > @@ -252,8 +252,8 @@ The standardized *attribute_target* names are `event`, `field`, `method`, `param - `event` — an event. - `field` — a field. A field-like event (i.e., one without accessors) ([§15.8.2](classes.md#1582-field-like-events)) and an automatically implemented property ([§15.7.4](classes.md#1574-automatically-implemented-properties)) can also have an attribute with this target. -- `method` — a constructor, finalizer, method, operator, property get and set accessors, indexer get and set accessors, and event add and remove accessors. A field-like event (i.e., one without accessors) can also have an attribute with this target. -- `param` — a property set accessor, an indexer set accessor, event add and remove accessors, and a parameter in a constructor, method, and operator. +- `method` — a constructor; finalizer; method; operator; property get, set, and init accessors; indexer get, set, and init accessors; and event add and remove accessors. A field-like event (i.e., one without accessors) can also have an attribute with this target. +- `param` — property set and init accessors, indexer set and init accessors, event add and remove accessors, and a parameter in a constructor, method, and operator. - `property` — a property and an indexer. - `return` — a delegate, method, operator, property get accessor, and indexer get accessor. - `type` — a delegate, class, struct, enum, and interface. @@ -273,7 +273,7 @@ Certain contexts permit the specification of an attribute on more than one targe - For an attribute on a get accessor declaration for a property or indexer declaration the default target is the associated method. Otherwise when the *attribute_target* is equal to: - `method` — the target is the associated method - `return` — the target is the return value -- For an attribute specified on a set accessor for a property or indexer declaration the default target is the associated method. Otherwise when the *attribute_target* is equal to: +- For an attribute specified on a set or init accessor for a property or indexer declaration the default target is the associated method. Otherwise when the *attribute_target* is equal to: - `method` — the target is the associated method - `param` — the target is the lone implicit parameter - For an attribute on an automatically implemented property declaration the default target is the property. Otherwise when the *attribute_target* is equal to: @@ -465,7 +465,7 @@ The compilation of an *attribute* with attribute class `T`, *positional_argumen - If `C` does not have public accessibility, then a compile-time error occurs. - For each *named_argument* `Arg` in `N`: - Let `Name` be the *identifier* of the *named_argument* `Arg`. - - `Name` shall identify a non-static read-write public field or property on `T`. If `T` has no such field or property, then a compile-time error occurs. + - `Name` shall identify a non-static read-write public field or a non-static public read-write or read-init property on `T`. If `T` has no such field or property, then a compile-time error occurs. - If any of the values within *positional_argument_list* `P` or one of the values within *named_argument_list* `N` is of type `System.String` and the value is not well-formed as defined by the Unicode Standard, it is implementation-defined whether the value compiled is equal to the run-time value retrieved ([§23.4.3](attributes.md#2343-run-time-retrieval-of-an-attribute-instance)). > *Note*: As an example, a string which contains a high surrogate UTF-16 code unit which isn’t immediately followed by a low surrogate code unit is not well-formed. *end note* - Store the following information (for run-time instantiation of the attribute) in the assembly output by the compiler as a result of compiling the program containing the attribute: the attribute class `T`, the instance constructor `C` on `T`, the *positional_argument_list* `P`, the *named_argument_list* `N`, and the associated program entity `E`, with the values resolved completely at compile-time. @@ -476,7 +476,7 @@ Using the terms defined in [§23.4.2](attributes.md#2342-compilation-of-an-attri - Follow the run-time processing steps for executing an *object_creation_expression* of the form `new T(P)`, using the instance constructor `C` and values as determined at compile-time. These steps either result in an exception, or produce an instance `O` of `T`. - For each *named_argument* `Arg` in `N`, in order: - - Let `Name` be the *identifier* of the *named_argument* `Arg`. If `Name` does not identify a non-static public read-write field or property on `O`, then an exception is thrown. + - Let `Name` be the *identifier* of the *named_argument* `Arg`. If `Name` does not identify a non-static public read-write field or a non-static public read-write or read-init property on `O`, then an exception is thrown. - Let `Value` be the result of evaluating the *attribute_argument_expression* of `Arg`. - If `Name` identifies a field on `O`, then set this field to `Value`. - Otherwise, Name identifies a property on `O`. Set this property to Value. diff --git a/standard/basic-concepts.md b/standard/basic-concepts.md index 1773345ea..183d4fd34 100644 --- a/standard/basic-concepts.md +++ b/standard/basic-concepts.md @@ -81,7 +81,7 @@ There are several different types of declaration spaces, as described in the fol - Each non-partial class, struct, or interface declaration creates a new declaration space. Each partial class, struct, or interface declaration contributes to a declaration space shared by all matching parts in the same program ([§16.2.4](structs.md#1624-partial-modifier)). Names are introduced into this declaration space through *class_member_declaration*s, *struct_member_declaration*s, *interface_member_declaration*s, or *type_parameter*s. Except for overloaded instance constructor declarations and static constructor declarations, a class, struct, or interface cannot contain a member declaration with the same name as the class, struct, or interface. A class, struct, or interface permits the declaration of overloaded methods and indexers. Furthermore, a class or struct permits the declaration of overloaded instance constructors and operators. For example, a class, struct, or interface may contain multiple method declarations with the same name, provided these method declarations differ in their signature ([§7.6](basic-concepts.md#76-signatures-and-overloading)). Note that base classes do not contribute to the declaration space of a class, and base interfaces do not contribute to the declaration space of an interface. Thus, a derived class or interface is allowed to declare a member with the same name as an inherited member. Such a member is said to ***hide*** the inherited member. - Each delegate declaration creates a new declaration space. Names are introduced into this declaration space through parameters (*fixed_parameter*s and *parameter_array*s) and *type_parameter*s. - Each enumeration declaration creates a new declaration space. Names are introduced into this declaration space through *enum_member_declarations*. -- Each method declaration, property declaration, property accessor declaration, indexer declaration, indexer accessor declaration, operator declaration, instance constructor declaration, anonymous function, and local function creates a new declaration space called a ***local variable declaration space***. Names are introduced into this declaration space through parameters (*fixed_parameter*s and *parameter_array*s) and *type_parameter*s. The set accessor for a property or an indexer introduces the name `value` as a parameter. The body of the function member, anonymous function, or local function, if any, is considered to be nested within the local variable declaration space. When a local variable declaration space and a nested local variable declaration space contain elements with the same name, within the scope of the nested local name, the outer local name is hidden ([§7.7.1](basic-concepts.md#771-general)) by the nested local name. +- Each method declaration, property declaration, property accessor declaration, indexer declaration, indexer accessor declaration, operator declaration, instance constructor declaration, anonymous function, and local function creates a new declaration space called a ***local variable declaration space***. Names are introduced into this declaration space through parameters (*fixed_parameter*s and *parameter_array*s) and *type_parameter*s. The set and init accessor for a property or an indexer introduces the name `value` as a parameter. The body of the function member, anonymous function, or local function, if any, is considered to be nested within the local variable declaration space. When a local variable declaration space and a nested local variable declaration space contain elements with the same name, within the scope of the nested local name, the outer local name is hidden ([§7.7.1](basic-concepts.md#771-general)) by the nested local name. - Additional local variable declaration spaces may occur within member declarations, anonymous functions and local functions. Names are introduced into these declaration spaces through *pattern*s, *declaration_expression*s, *declaration_statement*s and *exception_specifier*s. Local variable declaration spaces may be nested, but it is an error for a local variable declaration space and a nested local variable declaration space to contain elements with the same name. Thus, within a nested declaration space it is not possible to declare a local variable, local function or constant with the same name as a parameter, type parameter, local variable, local function or constant in an enclosing declaration space. It is possible for two declaration spaces to contain elements with the same name as long as neither declaration space contains the other. Local declaration spaces are created by the following constructs: - Each *variable_initializer* in a field and property declaration introduces its own local variable declaration space, that is not nested within any other local variable declaration space. - The body of a function member, anonymous function, or local function, if any, creates a local variable declaration space that is considered to be nested within the function’s local variable declaration space. diff --git a/standard/classes.md b/standard/classes.md index b02654945..311b50f71 100644 --- a/standard/classes.md +++ b/standard/classes.md @@ -1394,7 +1394,9 @@ T get_P(); void set_P(T value); ``` -Both signatures are reserved, even if the property is read-only or write-only. +Both signatures are reserved, even if the property has only one accessor. + +A set accessor and init accessor have the same signature; however, that for the init accessor also has an implementation-defined form of annotation to distinguish it from a set accessor. > *Example*: In the following code > @@ -1459,7 +1461,9 @@ T get_Item(L); void set_Item(L, T value); ``` -Both signatures are reserved, even if the indexer is read-only or write-only. +Both signatures are reserved, even if the indexer has only one accessor. + +A set accessor and init accessor have the same signature; however, that for the init accessor also has an implementation-defined form of annotation to distinguish it from a set accessor. Furthermore the member name `Item` is reserved. @@ -3373,14 +3377,18 @@ The differences between static and instance members are discussed further in [§ ### 15.7.3 Accessors -*Note*: This subclause applies to both properties ([§15.7](classes.md#157-properties)) and indexers ([§15.9](classes.md#159-indexers)). The subclause is written in terms of properties, when reading for indexers substitute indexer/indexers for property/properties and consult the list of differences between properties and indexers given in [§15.9.2](classes.md#1592-indexer-and-property-differences). *end note* +#### §accessors-general General + +*Note*: This subclause and its sibling subclauses apply to both properties ([§15.7](classes.md#157-properties)) and indexers ([§15.9](classes.md#159-indexers)). The clause is written in terms of properties, when reading for indexers substitute indexer/indexers for property/properties and consult the list of differences between properties and indexers given in [§15.9.2](classes.md#1592-indexer-and-property-differences). *end note* The *accessor_declarations* of a property specify the executable statements associated with writing and/or reading that property. ```ANTLR accessor_declarations - : get_accessor_declaration set_accessor_declaration? - | set_accessor_declaration get_accessor_declaration? + : get_accessor_declaration + (set_accessor_declaration | init_accessor_declaration)? + | (set_accessor_declaration | init_accessor_declaration) + get_accessor_declaration? ; get_accessor_declaration @@ -3391,6 +3399,10 @@ set_accessor_declaration : attributes? accessor_modifier? 'set' accessor_body ; +init_accessor_declaration + : attributes? accessor_modifier? 'init' accessor_body + ; + accessor_modifier : 'protected' | 'internal' @@ -3419,14 +3431,14 @@ ref_accessor_body ; ``` -The *accessor_declarations* consist of a *get_accessor_declaration*, a *set_accessor_declaration*, or both. Each accessor declaration consists of optional attributes, an optional *accessor_modifier*, the token `get` or `set`, followed by an *accessor_body*. +The *accessor_declarations* consist of either a *get_accessor_declaration*, optionally with a *set_accessor_declaration* or an *init_accessor_declaration*, or a *set_accessor_declaration* or *init_accessor_declaration* optionally with a *get_accessor_declaration*. Each accessor declaration consists of optional *attributes*, an optional *accessor_modifier*, the token `get`, `init`, or `set`, followed by an *accessor_body*. For a ref-valued property the *ref_get_accessor_declaration* consists optional attributes, an optional *accessor_modifier*, the token `get`, followed by an *ref_accessor_body*. The use of *accessor_modifier*s is governed by the following restrictions: - The *accessor_modifier* `readonly` is permitted only in a *property_declaration* or *indexer_declaration* that is contained directly by a *struct_declaration* ([§16.4.11](structs.md#16411-properties), [§16.4.13](structs.md#16413-indexers)). -- For a property or indexer that has no `override` modifier, an *accessor_modifier* is permitted only if the property or indexer has both a get and set accessor, and then is permitted only on one of those accessors. +- For a property or indexer that has no `override` modifier, an *accessor_modifier* is permitted only if the property or indexer has both a get and set or init accessor, and then is permitted only on one of those accessors. - For a property or indexer that includes an `override` modifier, an accessor shall match the *accessor_modifier*, if any, of the accessor being overridden. - The *accessor_modifier* shall declare an accessibility that is strictly more restrictive than the declared accessibility of the property or indexer itself. To be precise: - If the property or indexer has a declared accessibility of `public`, the accessibility declared by *accessor_modifier* may be either `private protected`, `protected internal`, `internal`, `protected`, or `private`. @@ -3476,11 +3488,17 @@ The body of a get accessor for a ref-valued property shall conform to the rules A set accessor corresponds to a method with a single value parameter of the property type and a `void` return type. The implicit parameter of a set accessor is always named `value`. When a property is referenced as the target of an assignment ([§12.23](expressions.md#1223-assignment-operators)), or as the operand of `++` or `–-` ([§12.8.16](expressions.md#12816-postfix-increment-and-decrement-operators), [§12.9.7](expressions.md#1297-prefix-increment-and-decrement-operators)), the set accessor is invoked with an argument that provides the new value ([§12.23.2](expressions.md#12232-simple-assignment)). The body of a set accessor shall conform to the rules for `void` methods described in [§15.6.11](classes.md#15611-method-body). In particular, return statements in the set accessor body are not permitted to specify an expression. Since a set accessor implicitly has a parameter named `value`, it is a compile-time error for a local variable or constant declaration in a set accessor to have that name. -Based on the presence or absence of the get and set accessors, a property is classified as follows: +An init accessor corresponds to a method with a single value parameter of the property type and a `void` return type. The implicit parameter of an init accessor is always named `value`. Only during the construction phase of an object (§init-accessors), and if an init accessor exists, when a property is referenced as the target of an assignment ([§12.21](expressions.md#1221-assignment-operators)), or as the operand of `++` or `–-` ([§12.8.15](expressions.md#12815-postfix-increment-and-decrement-operators), [§12.9.6](expressions.md#1296-prefix-increment-and-decrement-operators)), the init accessor is invoked with an argument that provides the new value ([§12.21.2](expressions.md#12212-simple-assignment)). The body of an init accessor shall conform to the rules for `void` methods described in [§15.6.11](classes.md#15611-method-body). In particular, return statements in the init accessor body are not permitted to specify an expression. Since an init accessor implicitly has a parameter named `value`, it is a compile-time error for a local variable or constant declaration in an init accessor to have that name. + +It is a compile-time error for a *property_declaration* containing an *init_accessor_declaration* to also have the *property_modifier* `static`. + +Based on the presence or absence of get, set, and init accessors, a property is classified as follows: - A property that includes both a get accessor and a set accessor is said to be a ***read-write property***. +- A property that includes both a get accessor and an init accessor is said to be a ***read-init property***. It is a compile-time error for a read-init property to be the target of an assignment except during the construction phase of an object (§init-accessors). - A property that has only a get accessor is said to be a ***read-only property***. It is a compile-time error for a read-only property to be the target of an assignment. - A property that has only a set accessor is said to be a ***write-only property***. Except as the target of an assignment, it is a compile-time error to reference a write-only property in an expression. +- A property that has only an init accessor is said to be an ***init-only property***. Except as the target of an assignment during the construction phase of an object (§init-accessors), it is a compile-time error to reference an init-only property in an expression. > *Note*: The pre- and postfix `++` and `--` operators and compound assignment operators cannot be applied to write-only properties, since these operators read the old value of their operand before they write the new one. *end note* @@ -3528,6 +3546,34 @@ Based on the presence or absence of the get and set accessors, a property is cla > > *end example* +For more information about set and get accessors, see §set-and-get-accessors. + +> *Example*: Consider the following, immutable type, which has auto-implemented properties: +> +> +> +> ```csharp +> struct Point +> { +> public int X { get; init; } +> public int Y { get; init; } +> } +> ``` +> +> A consumer can then use object initializers to create the object, as follows: +> +> +> +> ```csharp +> var p = new Point() { X = 42, Y = 13 }; +> ``` +> +> *end example* + +For more information about init accessors, see §init-accessors. + +#### §set-and-get-accessors Set and get accessors + The get and set accessors of a property are not distinct members, and it is not possible to declare the accessors of a property separately. > *Example*: The example @@ -3728,11 +3774,197 @@ Properties can be used to delay initialization of a resource until the moment it > > *end example* +#### §init-accessors Init accessors + +An instance property containing an *init_accessor_declaration* is considered settable during the construction phase of the object, except when in a local function or lambda. The ***construction phase of an object*** includes the following: + +- During execution of an *object_initializer* (§12.8.17.2.2) +- During evaluation of a *with_expression*’s *member_initializer_list* ([§xxx](plug in here link to v9 records grammar)) +- Inside an instance constructor of the containing or derived type, on `this` or `base` +- Inside the *init_accessor_declaration* of any property, on `this` or `base` +- Inside attribute usages with named parameters (§23.2.3) + +> *Example*: Consider the following: +> +> +> ```csharp +> class Student +> { +> public string? FirstName { get; init; } +> public string? LastName { get; init; } +> } +> +> class Consumption +> { +> static void Example() +> { +> var s = new Student() +> { +> FirstName = "Jared", +> LastName = "Parosns", // OK: LastName is settable +> }; +> s.LastName = "Parsons"; // Error: LastName is not settable +> } +> } +> ``` +> +> *end example* + +The rules around when init accessors are settable extend across type hierarchies. If the member is accessible and the object is known to be in the construction phase, then the member is settable. + +> *Example*: +> +> +> ```csharp +> class Base +> { +> public bool Value { get; init; } +> } +> +> class Derived : Base +> { +> public Derived() +> { +> Value = true; +> } +> } +> +> class Consumption +> { +> void Example() +> { +> var d = new Derived() { Value = true }; +> } +> } +> ``` +> +> *end example* + +At the point an init accessor is invoked, the instance is known to be in the construction phase. Hence an init accessor may take the following actions in addition to what a set accessor can do: + +1. Call other init accessors available through `this` or `base` +1. Assign `readonly` fields declared on the same type through `this` + +> *Example*: +> +> +> ```csharp +> class Complex +> { +> readonly int Field1; +> int Field2; +> int Prop1 { get; init ; } +> int Prop2 +> { +> get => 42; +> init +> { +> Field1 = 13; // OK +> Field2 = 13; // OK +> Prop1 = 13; // OK +> } +> } +> } +> ``` +> +> *end example* + +The ability to assign to `readonly` fields from an init accessor is limited to those fields declared on the same type as the accessor. It cannot be used to assign `readonly` fields in a base type. This rule ensures that type authors remain in control over the mutability behavior of their type. Developers who do not wish to utilize init accessors cannot be impacted from other types choosing to do so. + +> *Example*: +> +> +> ```csharp +> class Base +> { +> internal readonly int Field; +> internal int Property +> { +> get => Field; +> init => Field = value; // OK +> } +> internal int OtherProperty { get; init; } +> } +> +> class Derived : Base +> { +> internal readonly int DerivedField; +> +> internal int DerivedProperty +> { +> get => DerivedField; +> init +> { +> DerivedField = 42; // OK +> Property = 0; // OK +> Field = 13; // Error: Field is readonly +> } +> } +> +> public Derived() +> { +> Property = 42; // OK +> Field = 13; // Error: Field is readonly +> } +> } +> ``` +> +> *end example* + +An interface declaration can also participate in `init`-style initialization, via the following pattern: + + +```csharp + +interface IPerson +{ + string Name { get; init; } +} + +class Init +{ + void M() where T : IPerson, new() + { + var local = new T() + { + Name = "Jared" + }; + local.Name = "Jraed"; // Error + } +} +``` + +However, there are restrictions: + +- The init accessor can only be used on instance properties +- A property cannot contain both an init accessor and a set accessor +- All overrides of a property shall have `init` if the base has `init`. This rule also applies to interface implementation. + +Init accessors (both auto- and manually-implemented) are permitted on properties of `readonly struct`s, as well as `readonly` properties. Init accessors are not permitted to be marked `readonly` themselves, in both `readonly` and non-`readonly struct` types. + +> *Example*: +> +> +> ```csharp +> readonly struct ReadonlyStruct1 +> { +> public int Prop1 { get; init; } // OK +> } +> +> struct ReadonlyStruct2 +> { +> public readonly int Prop2 { get; init; } // OK +> public int Prop3 { get; readonly init; } // Error +> } +> ``` +> +> *end example* + ### 15.7.4 Automatically implemented properties -An automatically implemented property (or auto-property for short), is a non-abstract, non-extern, non-ref-valued property with semicolon-only *accessor_body*s. Auto-properties shall have a get accessor and may optionally have a set accessor. +An automatically implemented property (or auto-property for short), is a non-abstract, non-extern, non-ref-valued property with semicolon-only *accessor_body*s. An auto-property shall have a get accessor and may optionally have a set or init accessor. -When a property is specified as an automatically implemented property, a hidden backing field is automatically available for the property, and the accessors are implemented to read from and write to that backing field. The hidden backing field is inaccessible, it can be read and written only through the automatically implemented property accessors, even within the containing type. If the auto-property has no set accessor, the backing field is considered `readonly` ([§15.5.3](classes.md#1553-readonly-fields)). Just like a `readonly` field, a read-only auto-property may also be assigned to in the body of a constructor of the enclosing class. Such an assignment assigns directly to the read-only backing field of the property. +When a property is specified as an automatically implemented property, a hidden backing field is automatically available for the property, and the accessors are implemented to read from and write to that backing field. The hidden backing field is inaccessible, it can be read and written only through the automatically implemented property accessors, even within the containing type. If the auto-property has no set or init accessor, the backing field is considered `readonly` ([§15.5.3](classes.md#1553-readonly-fields)). Just like a `readonly` field, a read-only auto-property may also be assigned to in the body of a constructor of the enclosing class. Such an assignment assigns directly to the read-only backing field of the property. If the auto-property has an init accessor, the backing field may be assigned to during the construction phase of an object (§init-accessors). An auto-property may optionally have a *property_initializer*, which is applied directly to the backing field as a *variable_initializer* ([§17.7](arrays.md#177-array-initializers)). @@ -3939,7 +4171,7 @@ A property declaration that includes both the `abstract` and `override` modifier Abstract property declarations are only permitted in abstract classes ([§15.2.2.2](classes.md#15222-abstract-classes)) and interfaces ([§19.4.4](interfaces.md#1944-interface-properties)). The accessors of an inherited virtual property can be overridden in a derived class by including a property declaration that specifies an `override` directive. This is known as an ***overriding property declaration***. An overriding property declaration does not declare a new property. Instead, it simply specializes the implementations of the accessors of an existing virtual property. -The override declaration and the overridden base property are required to have the same declared accessibility. In other words, an override declaration shall not change the accessibility of the base property. However, if the overridden base property is protected internal and it is declared in a different assembly than the assembly containing the override declaration then the override declaration’s declared accessibility shall be protected. If the inherited property has only a single accessor (i.e., if the inherited property is read-only or write-only), the overriding property shall include only that accessor. If the inherited property includes both accessors (i.e., if the inherited property is read-write), the overriding property can include either a single accessor or both accessors. There shall be an identity conversion between the type of the overriding and the inherited property. +The override declaration and the overridden base property are required to have the same declared accessibility. In other words, an override declaration shall not change the accessibility of the base property. However, if the overridden base property is protected internal and it is declared in a different assembly than the assembly containing the override declaration then the override declaration’s declared accessibility shall be protected. If the inherited property has only a single accessor (i.e., if the inherited property is read-only, ninit-only, or write-only), the overriding property shall include only that accessor. If the inherited property includes two accessors (i.e., if the inherited property is read-write or read-init), the overriding property can include either a single accessor or both accessors. There shall be an identity conversion between the type of the overriding and the inherited property. An overriding property declaration may include the `sealed` modifier. Use of this modifier prevents a derived class from further overriding the property. The accessors of a sealed property are also sealed. @@ -3947,6 +4179,7 @@ Except for differences in declaration and invocation syntax, virtual, sealed, ov - A get accessor corresponds to a parameterless method with a return value of the property type and the same modifiers as the containing property. - A set accessor corresponds to a method with a single value parameter of the property type, a void return type, and the same modifiers as the containing property. +- An init accessor corresponds to a method with a single value parameter of the property type, a `void` return type, and the same modifiers as the containing property. > *Example*: In the following code > @@ -4031,6 +4264,31 @@ When a property is declared as an override, any overridden accessors shall be ac > > *end example* +When an init accessor appears in a virtual property, all overrides for it shall also be marked `init`. Likewise, it is not possible to override a set accessor with an init accessor. + +> *Example*: +> +> +> ```csharp +> class Base +> { +> public virtual int Property { get; init; } +> } +> +> class C1 : Base +> { +> public override int Property { get; init; } +> } +> +> class C2 : Base +> { +> // Error: Property must have init to override Base.Property +> public override int Property { get; set; } +> } +> ``` +> +> *end example* + ## 15.8 Events ### 15.8.1 General @@ -4537,13 +4795,14 @@ Indexers and properties are very similar in concept, but differ in the following - A property can be a static member, whereas an indexer is always an instance member. - A get accessor of a property corresponds to a method with no parameters, whereas a get accessor of an indexer corresponds to a method with the same parameter list as the indexer. - A set accessor of a property corresponds to a method with a single parameter named `value`, whereas a set accessor of an indexer corresponds to a method with the same parameter list as the indexer, plus an additional parameter named `value`. +- An init accessor of a property corresponds to a method with a single parameter named `value`, whereas an init accessor of an indexer corresponds to a method with the same formal parameter list as the indexer, plus an additional parameter named `value`. - It is a compile-time error for an indexer accessor to declare a local variable or local constant with the same name as an indexer parameter. - In an overriding property declaration, the inherited property is accessed using the syntax `base.P`, where `P` is the property name. In an overriding indexer declaration, the inherited indexer is accessed using the syntax `base[E]`, where `E` is a comma-separated list of expressions. - There is no concept of an “automatically implemented indexer.” It is an error to have a non-abstract, non-external indexer with semicolon *accessor_body*s. Aside from these differences, all rules defined in [§15.7.3](classes.md#1573-accessors), [§15.7.5](classes.md#1575-accessibility) and [§15.7.6](classes.md#1576-virtual-sealed-override-and-abstract-accessors) apply to indexer accessors as well as to property accessors. -This replacing of property/properties with indexer/indexers when reading [§15.7.3](classes.md#1573-accessors), [§15.7.5](classes.md#1575-accessibility) and [§15.7.6](classes.md#1576-virtual-sealed-override-and-abstract-accessors) applies to defined terms as well. Specifically, *read-write property* becomes ***read-write indexer***, *read-only property* becomes ***read-only indexer***, and *write-only property* becomes ***write-only indexer***. +This replacing of property/properties with indexer/indexers when reading [§15.7.3](classes.md#1573-accessors), [§15.7.5](classes.md#1575-accessibility) and [§15.7.6](classes.md#1576-virtual-sealed-override-and-abstract-accessors) applies to defined terms as well. Specifically, *read-write property* becomes ***read-write indexer***, *read-init property* becomes ***read-init indexer***, *read-only property* becomes ***read-only indexer***, and *write-only property* becomes ***write-only indexer***, and *init-only property* becomes ***init-only indexer***. ## 15.10 Operators diff --git a/standard/expressions.md b/standard/expressions.md index ecd659836..550558153 100644 --- a/standard/expressions.md +++ b/standard/expressions.md @@ -450,27 +450,27 @@ Once a particular function member has been identified at binding-time, possibly > > Property access > P -> The get accessor of the property P in the containing class or struct is invoked. A compile-time error occurs if P is write-only. If P is not static, the instance expression is this. +> The get accessor of the property P in the containing class or struct is invoked. A compile-time error occurs if P is write-only or init-only. If P is not static, the instance expression is this. > > > P = value -> The set accessor of the property P in the containing class or struct is invoked with the argument list (value). A compile-time error occurs if P is read-only. If P is not static, the instance expression is this. +> Scenario 1: The set accessor of the property P in the containing class or struct is invoked with the argument list (value). A compile-time error occurs if P is read-only. If P is not static, the instance expression is this. Scenario 2: The init accessor of the property P in the containing class or struct is invoked with the argument list (value). A compile-time error occurs if this usage is not during the construction phase of an object (§init-accessors). > > > T.P -> The get accessor of the property P in the class or struct T is invoked. A compile-time error occurs if P is not static or if P is write-only. +> The get accessor of the property P in the class or struct T is invoked. A compile-time error occurs if P is not static or if P is write-only or init-only. > > > T.P = value -> The set accessor of the property P in the class or struct T is invoked with the argument list (value). A compile-time error occurs if P is not static or if P is read-only. +> Scenario 1: The set accessor of the property P in the class or struct T is invoked with the argument list (value). A compile-time error occurs if P is not static or if P is read-only. Scenario 2: The init accessor of the property P in the class or struct T is invoked with the argument list (value). A compile-time error occurs if P is not static or if this usage is not during the construction phase of an object (§init-accessors). > > > e.P -> The get accessor of the property P in the class, struct, or interface given by the type of E is invoked with the instance expression e. A binding-time error occurs if P is static or if P is write-only. +> The get accessor of the property P in the class, struct, or interface given by the type of E is invoked with the instance expression e. A binding-time error occurs if P is static or if P is write-only or init-only. > > > e.P = value -> The set accessor of the property P in the class, struct, or interface given by the type of E is invoked with the instance expression e and the argument list (value). A binding-time error occurs if P is static or if P is read-only. +> Scenario 1: The set accessor of the property P in the class, struct, or interface given by the type of E is invoked with the instance expression e and the argument list (value). A binding-time error occurs if P is static or if P is read-only. Scenario 2: The init accessor of the property P in the class, struct, or interface given by the type of E is invoked with the instance expression e and the argument list (value). A binding-time error occurs if P is static or if this usage is not during the construction phase of an object (§init-accessors). > > > Event access @@ -500,12 +500,11 @@ Once a particular function member has been identified at binding-time, possibly > > Indexer access > e[x, y] -> Overload resolution is applied to select the best indexer in the class, struct, or interface given by the type of e. The get accessor of the indexer is invoked with the instance expression e and the argument list (x, y). A binding-time error occurs if the indexer is write-only. +> Overload resolution is applied to select the best indexer in the class, struct, or interface given by the type of e. The get accessor of the indexer is invoked with the instance expression e and the argument list (x, y). A binding-time error occurs if the indexer is write-only or init-only. > > > e[x, y] = value -> Overload resolution is applied to select the best indexer in the class, struct, or interface given by the type of e. The set accessor of the indexer is invoked with the instance expression e and the argument list (x, y, value). A binding-time error occurs if the indexer is read-only. -> +> Overload resolution is applied to select the best indexer in the class, struct, or interface given by the type of e. Scenario 1: The set accessor of the indexer is invoked with the instance expression e and the argument list (x, y, value). A binding-time error occurs if the indexer is read-only. Scenario 2: The init accessor of the indexer is invoked with the instance expression e and the argument list (x, y, value). A binding-time error occurs if this usage is not during the construction phase of an object (§init-accessors). > > > Operator invocation @@ -532,9 +531,9 @@ Once a particular function member has been identified at binding-time, possibly Every function member and delegate invocation includes an argument list, which provides actual values or variable references for the parameters of the function member. The syntax for specifying the argument list of a function member invocation depends on the function member category: -- For instance constructors, methods, indexers and delegates, the arguments are specified as an *argument_list*, as described below. For indexers, when invoking the set accessor, the argument list additionally includes the expression specified as the right operand of the assignment operator. - > *Note*: This additional argument is not used for overload resolution, just during invocation of the set accessor. *end note* -- For properties, the argument list is empty when invoking the get accessor, and consists of the expression specified as the right operand of the assignment operator when invoking the set accessor. +- For instance constructors, methods, indexers and delegates, the arguments are specified as an *argument_list*, as described below. For indexers, when invoking the set or init accessor, the argument list additionally includes the expression specified as the right operand of the assignment operator. + > *Note*: This additional argument is not used for overload resolution, just during invocation of the set or init accessor. *end note* +- For properties, the argument list is empty when invoking the get accessor, and consists of the expression specified as the right operand of the assignment operator when invoking the set or init accessor. - For events, the argument list consists of the expression specified as the right operand of the `+=` or `-=` operator. - For user-defined operators, the argument list consists of the single operand of the unary operator or the two operands of the binary operator. @@ -594,8 +593,8 @@ The corresponding parameters for function member arguments are established as fo - A positional argument where a parameter occurs at the same position in the parameter list corresponds to that parameter, unless the parameter is a parameter array and the function member is invoked in its expanded form. - A positional argument of a function member with a parameter array invoked in its expanded form, which occurs at or after the position of the parameter array in the parameter list, corresponds to an element in the parameter array. - A named argument corresponds to the parameter of the same name in the parameter list. - - For indexers, when invoking the set accessor, the expression specified as the right operand of the assignment operator corresponds to the implicit `value` parameter of the set accessor declaration. -- For properties, when invoking the get accessor there are no arguments. When invoking the set accessor, the expression specified as the right operand of the assignment operator corresponds to the implicit value parameter of the set accessor declaration. + - For indexers, when invoking the set or init accessor, the expression specified as the right operand of the assignment operator corresponds to the implicit `value` parameter of the set or init accessor declaration. +- For properties, when invoking the get accessor there are no arguments. When invoking the set or init accessor, the expression specified as the right operand of the assignment operator corresponds to the implicit value parameter of the set or init accessor declaration. - For user-defined unary operators (including conversions), the single operand corresponds to the single parameter of the operator declaration. - For user-defined binary operators, the left operand corresponds to the first parameter, and the right operand corresponds to the second parameter of the operator declaration. - An unnamed argument corresponds to no parameter when it is after an out-of-position named argument or a named argument that corresponds to a parameter array. @@ -2497,7 +2496,7 @@ The operand of a postfix increment or decrement operation shall be an expression If the *primary_expression* has the compile-time type `dynamic` then the operator is dynamically bound ([§12.3.3](expressions.md#1233-dynamic-binding)), the *post_increment_expression* or *post_decrement_expression* has the compile-time type `dynamic` and the following rules are applied at run-time using the run-time type of the *primary_expression*. -If the operand of a postfix increment or decrement operation is a property or indexer access, the property or indexer shall have both a get and a set accessor. If this is not the case, a binding-time error occurs. +If the operand of a postfix increment or decrement operation is a property or indexer access, the property or indexer shall have both a get and a set or init accessor. If this is not the case, a binding-time error occurs. Unary operator overload resolution ([§12.4.4](expressions.md#1244-unary-operator-overload-resolution)) is applied to select a specific operator implementation. Predefined `++` and `--` operators exist for the following types: `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, `long`, `ulong`, `char`, `float`, `double`, `decimal`, and any enum type. The predefined `++` operators return the value produced by adding `1` to the operand, and the predefined `--` operators return the value produced by subtracting `1` from the operand. In a checked context, if the result of this addition or subtraction is outside the range of the result type and the result type is an integral type or enum type, a `System.OverflowException` is thrown. @@ -2512,10 +2511,10 @@ The run-time processing of a postfix increment or decrement operation of the for - The value returned by the operator is converted to the type of `x` and stored in the location given by the earlier evaluation of `x`. - The saved value of `x` becomes the result of the operation. - If `x` is classified as a property or indexer access: - - The instance expression (if `x` is not `static`) and the argument list (if `x` is an indexer access) associated with `x` are evaluated, and the results are used in the subsequent get and set accessor invocations. + - The instance expression (if `x` is not `static`) and the argument list (if `x` is an indexer access) associated with `x` are evaluated, and the results are used in the subsequent get and set or init accessor invocations. - The get accessor of `x` is invoked and the returned value is saved. - The saved value of `x` is converted to the operand type of the selected operator and the operator is invoked with this value as its argument. - - The value returned by the operator is converted to the type of `x` and the set accessor of `x` is invoked with this value as its value argument. + - The value returned by the operator is converted to the type of `x` and the set or init accessor of `x` is invoked with this value as its value argument. - The saved value of `x` becomes the result of the operation. The `++` and `--` operators also support prefix notation ([§12.9.7](expressions.md#1297-prefix-increment-and-decrement-operators)). The result of `x++` or `x--` is the value of `x` *before* the operation, whereas the result of `++x` or `--x` is the value of `x` *after* the operation. In either case, `x` itself has the same value after the operation. @@ -3723,7 +3722,7 @@ pre_decrement_expression The operand of a prefix increment or decrement operation shall be an expression classified as a variable, a property access, or an indexer access. The result of the operation is a value of the same type as the operand. -If the operand of a prefix increment or decrement operation is a property or indexer access, the property or indexer shall have both a get and a set accessor. If this is not the case, a binding-time error occurs. +If the operand of a prefix increment or decrement operation is a property or indexer access, the property or indexer shall have both a get and a set or init accessor. If this is not the case, a binding-time error occurs. Unary operator overload resolution ([§12.4.4](expressions.md#1244-unary-operator-overload-resolution)) is applied to select a specific operator implementation. Predefined `++` and `--` operators exist for the following types: `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, `long`, `ulong`, `char`, `float`, `double`, `decimal`, and any enum type. The predefined `++` operators return the value produced by adding `1` to the operand, and the predefined `--` operators return the value produced by subtracting `1` from the operand. In a `checked` context, if the result of this addition or subtraction is outside the range of the result type and the result type is an integral type or enum type, a `System.OverflowException` is thrown. @@ -3736,10 +3735,10 @@ The run-time processing of a prefix increment or decrement operation of the form - The value of `x` is converted to the operand type of the selected operator and the operator is invoked with this value as its argument. - The value returned by the operator is converted to the type of `x`. The resulting value is stored in the location given by the evaluation of `x` and becomes the result of the operation. - If `x` is classified as a property or indexer access: - - The instance expression (if `x` is not `static`) and the argument list (if `x` is an indexer access) associated with `x` are evaluated, and the results are used in the subsequent get and set accessor invocations. + - The instance expression (if `x` is not `static`) and the argument list (if `x` is an indexer access) associated with `x` are evaluated, and the results are used in the subsequent get and set or init accessor invocations. - The get accessor of `x` is invoked. - The value returned by the get accessor is converted to the operand type of the selected operator and operator is invoked with this value as its argument. - - The value returned by the operator is converted to the type of `x`. The set accessor of `x` is invoked with this value as its value argument. + - The value returned by the operator is converted to the type of `x`. The set or init accessor of `x` is invoked with this value as its value argument. - This value also becomes the result of the operation. The `++` and `--` operators also support postfix notation ([§12.8.16](expressions.md#12816-postfix-increment-and-decrement-operators)). The result of `x++` or `x--` is the value of `x` before the operation, whereas the result of `++x` or `--x` is the value of `x` after the operation. In either case, `x` itself has the same value after the operation. @@ -6764,9 +6763,9 @@ The type of a simple assignment `x = y` is the type of an assignment to `x` of ` - If `x` is a tuple expression `(x1, ..., xn)`, and `y` can be deconstructed to a tuple expression `(y1, ..., yn)` with `n` elements ([§12.7](expressions.md#127-deconstruction)), and each assignment to `xi` of `yi` has the type `Ti`, then the assignment has the type `(T1, ..., Tn)`. - Otherwise, if `x` is classified as a variable, the variable is not `readonly`, `x` has a type `T`, and `y` has an implicit conversion to `T`, then the assignment has the type `T`. -- Otherwise, if `x` is classified as an implicitly typed variable (i.e. an implicitly typed declaration expression) and `y` has a type `T`, then the inferred type of the variable is `T`, and the assignment has the type `T`. -- Otherwise, if `x` is classified as a property or indexer access, the property or indexer has an accessible set accessor, `x` has a type `T`, and `y` has an implicit conversion to `T`, then the assignment has the type `T`. -- Otherwise the assignment is not valid and a binding-time error occurs. +- Otherwise, if `x` is classified as an implicitly typed variable (i.e., an implicitly typed declaration expression) and `y` has a type `T`, then the inferred type of the variable is `T`, and the assignment has the type `T`. +- Otherwise, if `x` is classified as a property or indexer access, the property or indexer has an accessible set or init accessor, `x` has a type `T`, and `y` has an implicit conversion to `T`, then the assignment has the type `T`. +- Otherwise, the assignment is not valid, and a binding-time error occurs. The run-time processing of a simple assignment of the form `x = y` with type `T` is performed as an assignment to `x` of `y` with type `T`, which consists of the following recursive steps: @@ -6776,7 +6775,7 @@ The run-time processing of a simple assignment of the form `x = y` with type `T` - The value resulting from the evaluation and conversion of `y` is stored into the location given by the evaluation of `x`, and is yielded as a result of the assignment. - If `x` is classified as a property or indexer access: - `y` is evaluated and, if required, converted to `T` through an implicit conversion ([§10.2](conversions.md#102-implicit-conversions)). - - The set accessor of `x` is invoked with the value resulting from the evaluation and conversion of `y` as its value argument. + - The set or init accessor of `x` is invoked with the value resulting from the evaluation and conversion of `y` as its value argument. - The value resulting from the evaluation and conversion of `y` is yielded as the result of the assignment. - If `x` is classified as a tuple `(x1, ..., xn)` with arity `n`: - `y` is deconstructed with `n` elements to a tuple expression `e`. @@ -6953,7 +6952,7 @@ The term “evaluated only once” means that in the evaluation of `x «op» y > *Example*: In the assignment `A()[B()] += C()`, where `A` is a method returning `int[]`, and `B` and `C` are methods returning `int`, the methods are invoked only once, in the order `A`, `B`, `C`. *end example* -When the left operand of a compound assignment is a property access or indexer access, the property or indexer shall have both a get accessor and a set accessor. If this is not the case, a binding-time error occurs. +When the left operand of a compound assignment is a property access or indexer access, the property or indexer shall have both a get accessor and a set or init accessor. If this is not the case, a binding-time error occurs. The second rule above permits `x «op»= y` to be evaluated as `x = (T)(x «op» y)` in certain contexts. The rule exists such that the predefined operators can be used as compound operators when the left operand is of type `sbyte`, `byte`, `short`, `ushort`, or `char`. Even when both arguments are of one of those types, the predefined operators produce a result of type `int`, as described in [§12.4.7.3](expressions.md#12473-binary-numeric-promotions). Thus, without a cast it would not be possible to assign the result to the left operand. diff --git a/standard/interfaces.md b/standard/interfaces.md index 5ed62239f..fda92400d 100644 --- a/standard/interfaces.md +++ b/standard/interfaces.md @@ -439,7 +439,7 @@ Interface properties are declared using *property_declaration*s ([§15.7.1](clas > *Note*: As an interface cannot contain instance fields, an interface property cannot be an instance auto-property, as that would require the declaration of implicit hidden instance fields. *end note* -- The type of an interface property shall be output-safe if there is a get accessor, and shall be input-safe if there is a set accessor. +- The type of an interface property shall be output-safe if there is a get accessor, and shall be input-safe if there is a set or init accessor. - An interface method declaration that has a block body or expression body as a *method_body* is `virtual`; the `virtual` modifier is not required, but is allowed. - An instance *property_declaration* that has no implementation is `abstract`; the `abstract` modifier is not required, but is allowed. It is *never* considered to be an automatically implemented property ([§15.7.4](classes.md#1574-automatically-implemented-properties)). @@ -470,7 +470,7 @@ Interface indexers are declared using *indexer_declaration*s ([§15.9](classes.m > *Note*: Output parameters are required to be input-safe due to common implementation restrictions. *end note* -- The type of an interface indexer shall be output-safe if there is a get accessor, and shall be input-safe if there is a set accessor. +- The type of an interface indexer shall be output-safe if there is a get accessor, and shall be input-safe if there is a set or init accessor. ### 19.4.7 Interface operators diff --git a/standard/lexical-structure.md b/standard/lexical-structure.md index b025b9120..ca98425b5 100644 --- a/standard/lexical-structure.md +++ b/standard/lexical-structure.md @@ -606,18 +606,19 @@ A ***contextual keyword*** is an identifier-like sequence of characters that has ```ANTLR contextual_keyword - : 'add' | 'alias' | 'ascending' | 'async' | 'await' - | 'by' | 'descending' | 'dynamic' | 'equals' | 'from' - | 'get' | 'global' | 'group' | 'into' | 'join' - | 'let' | 'nameof' | 'notnull' | 'on' | 'orderby' - | 'partial' | 'remove' | 'select' | 'set' | 'unmanaged' - | 'value' | 'var' | 'when' | 'where' | 'yield' + : 'add' | 'alias' | 'ascending' | 'async' | 'await' + | 'by' | 'descending' | 'dynamic' | 'equals' | 'from' + | 'get' | 'global' | 'group' | 'init' | 'into' + | 'join' | 'let' | 'nameof' | 'notnull' | 'on' + | 'orderby' | 'partial' | 'remove' | 'select' | 'set' + | 'unmanaged' | 'value' | 'var' | 'when' | 'where' + | 'yield' ; ``` > *Note*: The rules *keyword* and *contextual_keyword* are parser rules as they do not introduce new token kinds. All keywords and contextual keywords are defined by implicit lexical rules as they occur as literal strings in the grammar ([§6.2.3](lexical-structure.md#623-lexical-grammar)). *end note* -In most cases, the syntactic location of contextual keywords is such that they can never be confused with ordinary identifier usage. For example, within a property declaration, the `get` and `set` identifiers have special meaning ([§15.7.3](classes.md#1573-accessors)). An identifier other than `get` or `set` is never permitted in these locations, so this use does not conflict with a use of these words as identifiers. +In most cases, the syntactic location of contextual keywords is such that they can never be confused with ordinary identifier usage. For example, within a property declaration, the `get`, `init`, and `set` identifiers have special meaning ([§15.7.3](classes.md#1573-accessors)). An identifier other than `get`, `init`, or `set` is never permitted in these locations, so this use does not conflict with a use of these words as identifiers. In certain cases the grammar is not enough to distinguish contextual keyword usage from identifiers. In all such cases it will be specified how to disambiguate between the two. For example, the contextual keyword `var` in implicitly typed local variable declarations ([§13.6.2](statements.md#1362-local-variable-declarations)) might conflict with a declared type called `var`, in which case the declared name takes precedence over the use of the identifier as a contextual keyword. diff --git a/standard/portability-issues.md b/standard/portability-issues.md index 40f1f9eb8..8e2bd8bcf 100644 --- a/standard/portability-issues.md +++ b/standard/portability-issues.md @@ -41,6 +41,8 @@ A conforming implementation is required to document its choice of behavior in ea 1. The value returned when a stack allocation of size zero is made. ([§12.8.22](expressions.md#12822-stack-allocation)) 1. Whether a `System.ArithmeticException` (or a subclass thereof) is thrown or the overflow goes unreported with the resulting value being that of the left operand, when in an `unchecked` context and the left operand of an integer division is the maximum negative `int` or `long` value and the right operand is `–1`. ([§12.12.3](expressions.md#12123-division-operator)) 1. When a `System.ArithmeticException` (or a subclass thereof) is thrown when performing a decimal remainder operation. ([§12.12.4](expressions.md#12124-remainder-operator)) +1. The mechanism for distinguishing a property’s set accessor signature from that of an init accessor ([§15.3.10.2](classes.md#153102-member-names-reserved-for-properties)). +1. The mechanism for distinguishing an indexer’s set accessor signature from that of an init accessor ([§15.3.10.4](classes.md#153104-member-names-reserved-for-indexers)). 1. The impact of thread termination when a thread has no handler for an exception, and the thread is itself terminated. ([§13.10.6](statements.md#13106-the-throw-statement)) 1. The mechanism by which linkage to an external method is achieved. ([§15.6.8](classes.md#1568-external-methods)) 1. The impact of thread termination when no matching `catch` clause is found for an exception and the code that initially started that thread is reached. ([§22.4](exceptions.md#224-how-exceptions-are-handled)). diff --git a/tools/example-templates/additional-files/PointStructWithInit.cs b/tools/example-templates/additional-files/PointStructWithInit.cs new file mode 100644 index 000000000..d2b51121c --- /dev/null +++ b/tools/example-templates/additional-files/PointStructWithInit.cs @@ -0,0 +1,5 @@ +struct Point +{ + public int X { get; init; } + public int Y { get; init; } +} \ No newline at end of file