From eecb2833163f70ee13fac0959cb951d50169fea5 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Fri, 30 Jan 2026 13:11:55 -0500 Subject: [PATCH 1/9] support parameterless struct constructors --- standard/classes.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/standard/classes.md b/standard/classes.md index e2ee11ab2..c17be362c 100644 --- a/standard/classes.md +++ b/standard/classes.md @@ -5272,7 +5272,11 @@ An instance constructor initializer cannot access the instance being created. Th ### 15.11.3 Instance variable initializers -When a non-extern instance constructor has no constructor initializer, or it has a constructor initializer of the form `base(...)`, that constructor implicitly performs the initializations specified by the *variable_initializer*s of the instance fields declared in its class. This corresponds to a sequence of assignments that are executed immediately upon entry to the constructor and before the implicit invocation of the direct base class constructor. The variable initializers are executed in the textual order in which they appear in the class declaration ([§15.5.6](classes.md#1556-variable-initializers)). +When a non-extern class instance constructor has no constructor initializer, or it has a constructor initializer of the form `base(...)`, that constructor implicitly performs the initializations specified by the *variable_initializer*s of the instance fields declared in its class. This corresponds to a sequence of assignments that are executed immediately upon entry to the constructor and before the implicit invocation of the direct base class constructor. The variable initializers are executed in the textual order in which they appear in the class declaration ([§15.5.6](classes.md#1556-variable-initializers)). + +When a struct instance constructor has no constructor initializer, that constructor implicitly performs the initializations specified by the *variable_initializer*s of the instance fields declared in its struct. This corresponds to a sequence of assignments that are executed immediately upon entry to the constructor. + +When a struct instance constructor has a `this()` constructor initializer that represents the _default parameterless constructor_, the declared constructor implicitly clears all instance fields and performs the initializations specified by the *variable_initializer*s of the instance fields declared in its struct. Immediately upon entry to the constructor, all value type fields are set to their default value and all reference type fields are set to `null`. Immediately after that, a sequence of assignments corresponding to the *variable_initializer*s are executed. Variable initializers are not required to be executed by extern instance constructors. From 25e4fd5a7e57a49b1cf3be54092620b85f27ff02 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Fri, 30 Jan 2026 13:21:04 -0500 Subject: [PATCH 2/9] support parameterless struct constructors --- standard/structs.md | 61 +++++++++++++++++++++++++++++++++------------ 1 file changed, 45 insertions(+), 16 deletions(-) diff --git a/standard/structs.md b/standard/structs.md index c5112c0f1..2b9f34fe5 100644 --- a/standard/structs.md +++ b/standard/structs.md @@ -192,8 +192,6 @@ Structs differ from classes in several important ways: - The default value of a struct is the value produced by setting all fields to their default value ([§16.4.5](structs.md#1645-default-values)). - Boxing and unboxing operations are used to convert between a struct type and certain reference types ([§16.4.6](structs.md#1646-boxing-and-unboxing)). - The meaning of `this` is different within struct members ([§16.4.7](structs.md#1647-meaning-of-this)). -- Instance field declarations for a struct are not permitted to include variable initializers ([§16.4.8](structs.md#1648-field-initializers)). -- A struct is not permitted to declare a parameterless instance constructor ([§16.4.9](structs.md#1649-constructors)). - A struct is not permitted to declare a finalizer. - Event declarations, property declarations, property accessors, indexer declarations, and method declarations are permitted to have the modifier `readonly` while that is not generally permitted for those same member kinds in classes. @@ -427,64 +425,95 @@ Similarly, boxing never implicitly occurs when accessing a member on a constrain ### 16.4.8 Field initializers -As described in [§16.4.5](structs.md#1645-default-values), the default value of a struct consists of the value that results from setting all value type fields to their default value and all reference type fields to `null`. For this reason, a struct does not permit instance field declarations to include variable initializers. This restriction applies only to instance fields. Static fields of a struct are permitted to include variable initializers. +As described in [§16.4.5](structs.md#1645-default-values), the default value of a struct consists of the value that results from setting all value type fields to their default value and all reference type fields to `null`. Static and instance fields of a struct are permitted to include variable initializers; however, in the case of an instance field initializer, at least one instance constructor shall also be declared, or for a record struct, a *delimited_parameter_list* shall be present. -> *Example*: The following +> *Example*: > -> +> > ```csharp +> Console.WriteLine($"Point is {new Point()}"); +> > struct Point > { -> public int x = 1; // Error, initializer not permitted -> public int y = 1; // Error, initializer not permitted +> public int x = 1; +> public int y = 1; +> +> public Point() { } +> +> public override string ToString() +> { +> return "(" + x + ", " + y + ")"; +> } > } > ``` > -> is in error because the instance field declarations include variable initializers. +> ```console +> Point is (1, 1) +> ``` > > *end example* +When a struct instance constructor has no constructor initializer, that constructor implicitly performs the initializations specified by the *variable_initializer*s of the instance fields declared in its struct. This corresponds to a sequence of assignments that are executed immediately upon entry to the constructor. + +When a struct instance constructor has a `this()` constructor initializer that represents the default parameterless constructor, the declared constructor implicitly clears all instance fields and performs the initializations specified by the *variable_initializer*s of the instance fields declared in its struct. Immediately upon entry to the constructor, all value type fields are set to their default value and all reference type fields are set to `null`. Immediately after that, a sequence of assignments corresponding to the *variable_initializer*s are executed. + A *field_declaration* declared directly inside a *struct_declaration* having the *struct_modifier* `readonly` shall have the *field_modifier* `readonly`. ### 16.4.9 Constructors -Unlike a class, a struct is not permitted to declare a parameterless instance constructor. Instead, every struct implicitly has a parameterless instance constructor, which always returns the value that results from setting all value type fields to their default value and all reference type fields to `null` ([§8.3.3](types.md#833-default-constructors)). A struct can declare instance constructors having parameters. +A struct can declare instance constructors, with zero or more parameters. If a struct has no explicitly declared parameterless instance constructor, one is synthesized, with public accessibility, which always returns the value that results from setting all value type fields to their default value and all reference type fields to `null` ([§8.3.3](types.md#833-default-constructors)). In such a case, any instance field initializers are ignored when that constructor executes. -> *Example*: Given the following +An explicitly declared parameterless instance constructor shall have public accessibility. + + +> *Example*: Given the following: > -> +> > ```csharp +> using System; > struct Point > { -> int x, y; +> int x = -1, y = -2; > > public Point(int x, int y) > { > this.x = x; > this.y = y; > } +> +> public override string ToString() +> { +> return "(" + x + ", " + y + ")"; +> } > } > > class A > { > static void Main() > { -> Point p1 = new Point(); -> Point p2 = new Point(0, 0); +> Console.WriteLine($"Point is {new Point()}"); +> Console.WriteLine($"Point is {new Point(0,0)}"); > } > } > ``` > -> the statements both create a `Point` with `x` and `y` initialized to zero. +> ```console +> Point is (0, 0) +> Point is (0, 0) +> ``` +> +> the statements both create a `Point` with `x` and `y` initialized to zero, which in the case of the call to the parameterless instance constructor, may be surprising, as both instance fields have initializers, but they are *not* executed. > > *end example* -A struct instance constructor is not permitted to include a constructor initializer of the form `base(`*argument_list*`)`, where *argument_list* is optional. +A struct instance constructor is not permitted to include a constructor initializer of the form `base(`*argument_list*`)`, where *argument_list* is optional. The execution of an instance constructor shall not result in the execution of a constructor in the struct’s base type `System.ValueType`. The `this` parameter of a struct instance constructor corresponds to an output parameter of the struct type. As such, `this` shall be definitely assigned ([§9.4](variables.md#94-definite-assignment)) at every location where the constructor returns. Similarly, it cannot be read (even implicitly) in the constructor body before being definitely assigned. If the struct instance constructor specifies a constructor initializer, that initializer is considered a definite assignment to this that occurs prior to the body of the constructor. Therefore, the body itself has no initialization requirements. +Instance fields (other than `fixed` fields) shall be definitely assigned in struct instance constructors that do not have a `this()` initializer. + > *Example*: Consider the instance constructor implementation below: > > From 0d7d17a4b0d3eadc1bafc5b57fa0123b1fdc1d3e Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Fri, 30 Jan 2026 13:28:20 -0500 Subject: [PATCH 3/9] md formatting --- standard/classes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/standard/classes.md b/standard/classes.md index c17be362c..aaebd5119 100644 --- a/standard/classes.md +++ b/standard/classes.md @@ -5276,7 +5276,7 @@ When a non-extern class instance constructor has no constructor initializer, or When a struct instance constructor has no constructor initializer, that constructor implicitly performs the initializations specified by the *variable_initializer*s of the instance fields declared in its struct. This corresponds to a sequence of assignments that are executed immediately upon entry to the constructor. -When a struct instance constructor has a `this()` constructor initializer that represents the _default parameterless constructor_, the declared constructor implicitly clears all instance fields and performs the initializations specified by the *variable_initializer*s of the instance fields declared in its struct. Immediately upon entry to the constructor, all value type fields are set to their default value and all reference type fields are set to `null`. Immediately after that, a sequence of assignments corresponding to the *variable_initializer*s are executed. +When a struct instance constructor has a `this()` constructor initializer that represents the *default parameterless constructor*, the declared constructor implicitly clears all instance fields and performs the initializations specified by the *variable_initializer*s of the instance fields declared in its struct. Immediately upon entry to the constructor, all value type fields are set to their default value and all reference type fields are set to `null`. Immediately after that, a sequence of assignments corresponding to the *variable_initializer*s are executed. Variable initializers are not required to be executed by extern instance constructors. From dcdc62a897fb25adccdffa9ef0322bf0b39a2a94 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Tue, 10 Feb 2026 15:50:07 -0500 Subject: [PATCH 4/9] Remove unnecessary blank lines in classes.md --- standard/classes.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/standard/classes.md b/standard/classes.md index aaebd5119..81f5e0c4c 100644 --- a/standard/classes.md +++ b/standard/classes.md @@ -6487,6 +6487,3 @@ A positional record class ([§15.2.1](classes.md#1521-general)) with at least on > ``` > > *end example* - - - From 287bdb1f62230ad24f9aff6ee4aad31dbe7ee6da Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Tue, 10 Feb 2026 15:51:28 -0500 Subject: [PATCH 5/9] Remove empty line before example section in structs.md Removed an empty line before the example section in structs.md. --- standard/structs.md | 1 - 1 file changed, 1 deletion(-) diff --git a/standard/structs.md b/standard/structs.md index 2b9f34fe5..857377545 100644 --- a/standard/structs.md +++ b/standard/structs.md @@ -465,7 +465,6 @@ A struct can declare instance constructors, with zero or more parameters. If a s An explicitly declared parameterless instance constructor shall have public accessibility. - > *Example*: Given the following: > > From db338830f162e8896a13964556769a63b89db12f Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Thu, 26 Mar 2026 13:32:33 -0400 Subject: [PATCH 6/9] Fix contradictions from missing edits MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three passages directly contradict the feature: - **Line 268** — *"it is not possible for a struct type to contain an explicit declaration of a parameterless constructor"* — This prohibition must be replaced with text that permits explicit parameterless constructors, requires them to be public, and cross-references §16.4.9. - **Line 232** — *"All value types implicitly declare a public parameterless instance constructor called the default constructor."* — Needs qualification: this applies only when no explicit parameterless constructor is declared. - **Lines 251–252** — Note claims `default(S)` and `new S()` produce the same result. This is no longer true when a struct has an explicit parameterless constructor with field initializers. Must be qualified: only holds when no explicit parameterless constructor is declared. Rewrite the opening to distinguish synthesized vs. explicit parameterless constructors. Qualify the note. Replace the final paragraph's prohibition with a permissive statement. --- standard/types.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/standard/types.md b/standard/types.md index 0ce6b1e5d..178b6a3a7 100644 --- a/standard/types.md +++ b/standard/types.md @@ -229,7 +229,7 @@ Note that `System.ValueType` is not itself a *value_type*. Rather, it is a *clas ### 8.3.3 Default constructors -All value types implicitly declare a public parameterless instance constructor called the ***default constructor***. The default constructor returns a zero-initialized instance known as the ***default value*** for the value type: +All value types have a public parameterless instance constructor called the ***default constructor***. For struct types that do not explicitly declare a parameterless instance constructor, the default constructor is synthesized by the compiler. The default constructor returns a zero-initialized instance known as the ***default value*** for the value type: - For all *simple_type*s, the default value is the value produced by a bit pattern of all zeros: - For `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, `nint`, `nuint`, `long`, and `ulong`, the default value is `0`. @@ -244,7 +244,7 @@ All value types implicitly declare a public parameterless instance constructor c Like any other instance constructor, the default constructor of a value type is invoked using the `new` operator. -> *Note*: For efficiency reasons, this requirement is not intended to actually have the implementation generate a constructor call. For value types, the default value expression ([§12.8.21](expressions.md#12821-default-value-expressions)) produces the same result as using the default constructor. *end note* +> *Note*: For efficiency reasons, this requirement is not intended to actually have the implementation generate a constructor call. For value types that do not have an explicitly declared parameterless instance constructor, the default value expression ([§12.8.21](expressions.md#12821-default-value-expressions)) produces the same result as using the default constructor. For struct types that declare an explicit parameterless instance constructor, `default` produces the zero-initialized default value, while `new S()` invokes the declared constructor, and the results may differ. *end note* @@ -265,7 +265,7 @@ Like any other instance constructor, the default constructor of a value type is > > *end example* -Because every value type implicitly has a public parameterless instance constructor, it is not possible for a struct type to contain an explicit declaration of a parameterless constructor. A struct type is however permitted to declare parameterized instance constructors ([§16.4.9](structs.md#1649-constructors)). +A struct type is permitted to declare instance constructors, including a parameterless instance constructor. An explicitly declared parameterless instance constructor shall have public accessibility ([§16.4.9](structs.md#1649-constructors)). ### 8.3.4 Struct types From 008dcb6416daba1a8bb60757e4a3919dea13ec09 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Thu, 26 Mar 2026 14:16:15 -0400 Subject: [PATCH 7/9] Default values discusses parameterless constructors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Line 298 says: *"Unlike a class, a struct is not permitted to declare a parameterless instance constructor."* — Directly contradicts the feature. Rewrite to say: when a struct does not declare an explicit parameterless constructor, one is synthesized that returns the zeroed value. Note that `default` always produces the zeroed value, while `new S()` invokes the declared constructor when one exists. --- standard/structs.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/standard/structs.md b/standard/structs.md index 857377545..698ae0aa4 100644 --- a/standard/structs.md +++ b/standard/structs.md @@ -295,7 +295,7 @@ As described in [§9.3](variables.md#93-default-values), several kinds of variab > > *end example* -The default value of a struct corresponds to the value returned by the default constructor of the struct ([§8.3.3](types.md#833-default-constructors)). Unlike a class, a struct is not permitted to declare a parameterless instance constructor. Instead, every struct implicitly has a parameterless instance constructor, which always returns the value that results from setting all fields to their default values. +The default value of a struct corresponds to the value returned by the default constructor of the struct ([§8.3.3](types.md#833-default-constructors)). When a struct does not declare an explicit parameterless instance constructor, the default constructor is synthesized and always returns the value that results from setting all fields to their default values. The `default` expression always produces the zero-initialized default value, even when a struct declares an explicit parameterless instance constructor ([§16.4.9](structs.md#1649-constructors)). > *Note*: Structs should be designed to consider the default initialization state a valid state. In the example > From 58bc12de07581d462513b91d6fb190f25ef6f02a Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Thu, 26 Mar 2026 14:57:09 -0400 Subject: [PATCH 8/9] binding resolution fixes The first bullet says for value types with no arguments, the result is *"the default value for `T`"*. When a struct has an explicit parameterless constructor, `new S()` invokes that constructor and the result may differ from the zeroed default value. Changes: Qualify the first bullet: when `T` is a *struct_type* with an explicitly declared parameterless constructor, the expression invokes that constructor (falling through to overload resolution in the third bullet), rather than unconditionally returning the default value. --- standard/expressions.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/standard/expressions.md b/standard/expressions.md index d5bf99b47..d3a54a73b 100644 --- a/standard/expressions.md +++ b/standard/expressions.md @@ -2635,7 +2635,8 @@ If any of the arguments in the optional *argument_list* has the compile-time typ The binding-time processing of an *object_creation_expression* of the form `new T(A)`, where the specified or implied type `T` is a *class_type*, or a *value_type*, and `A` is an optional *argument_list*, consists of the following steps: - If `T` is a *value_type* and `A` is not present: - - The *object_creation_expression* is a default constructor invocation. The result of the *object_creation_expression* is a value of type `T`, namely the default value for `T` as defined in [§8.3.3](types.md#833-default-constructors). + - If `T` is a *struct_type* that has an explicitly declared parameterless instance constructor, that constructor is a candidate and is selected via overload resolution as described below. + - Otherwise, the *object_creation_expression* is a default constructor invocation. The result of the *object_creation_expression* is a value of type `T`, namely the default value for `T` as defined in [§8.3.3](types.md#833-default-constructors). - Otherwise, if `T` is a *type_parameter* and `A` is not present: - If no value type constraint or constructor constraint ([§15.2.5](classes.md#1525-type-parameter-constraints)) has been specified for `T`, a binding-time error occurs. - The result of the *object_creation_expression* is a value of the run-time type that the type parameter has been bound to, namely the result of invoking the default constructor of that type. The run-time type may be a reference type or a value type. From 4d8d9d4c9d76328471e4f363caa891afe5f2a16a Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Thu, 26 Mar 2026 16:36:14 -0400 Subject: [PATCH 9/9] fix lint --- standard/attributes.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/standard/attributes.md b/standard/attributes.md index 8ab493677..04a13fcbf 100644 --- a/standard/attributes.md +++ b/standard/attributes.md @@ -1192,5 +1192,3 @@ For interoperation with other languages, an indexer may be implemented using ind > Now, the indexer’s name is `TheItem`. > > *end example* - -