From 9ba90cb1fb3faa7dddad46ca7079fb19f569f8f6 Mon Sep 17 00:00:00 2001 From: Jon Skeet Date: Tue, 27 Jan 2026 09:21:44 +0000 Subject: [PATCH 1/8] Describe the impact of ? on arrays of arrays Alternative to #1386. Fixes #1385. --- standard/arrays.md | 56 +++++++++++++++++++++++++++++++++++++++++----- standard/types.md | 26 +++++++++++++++++++-- 2 files changed, 74 insertions(+), 8 deletions(-) diff --git a/standard/arrays.md b/standard/arrays.md index 7f0611678..9934eb7b3 100644 --- a/standard/arrays.md +++ b/standard/arrays.md @@ -31,20 +31,64 @@ Every array type is a reference type ([§8.2](types.md#82-reference-types)). The The grammar productions for array types are provided in [§8.2.1](types.md#821-general). -An array type is written as a *non_array_type* followed by one or more *rank_specifier*s. +An array type is written as a *non_array_type* followed by one or more *rank_specifier*s, or an *array_type* followed by a *nullable_type_annotation* followed by one or more *rank_specifier*s. The latter production is used to represent array types where the element type is includes a nullable array type. A *non_array_type* is any *type* that is not itself an *array_type*. -The rank of an array type is given by the leftmost *rank_specifier* in the *array_type*: A *rank_specifier* indicates that the array is an array with a rank of one plus the number of “`,`” tokens in the *rank_specifier*. +When determining the rank and element type of array type as specified below, only the *rank_specifier*s in the top-most production are considered, so in the production `array_type nullable_type_annotation rank_specifier+`, any `rank_specifier` within the `array_type` is ignored. -The element type of an array type is the type that results from deleting the leftmost *rank_specifier*: +The rank of an array type is given by the leftmost *rank_specifier* in the *array_type*: A *rank_specifier* indicates that the array is an array with a rank of one plus the number of “`,`” tokens in the *rank_specifier*. -- An array type of the form `T[R]` is an array with rank `R` and a non-array element type `T`. -- An array type of the form `T[R][R₁]...[Rₓ]` is an array with rank `R` and an element type `T[R₁]...[Rₓ]`. +The element type of an array type is the type that results from deleting the leftmost *rank_specifier*. In effect, the *rank_specifier*s are read from left to right *before* the final non-array element type. -> *Example*: The type in `T[][,,][,]` is a single-dimensional array of three-dimensional arrays of two-dimensional arrays of `int`. *end example* +> *Example*: The following code shows several variable declarations, including a mixture of single-dimensional arrays, multi-dimensional arrays, and arrays of arrays, with some using nullable reference types. In each case, the rank and element type is described, and then demonstrated with a second variable declaration which is initialized using an element access expression. +> +> +> ```csharp +> // Rank 1, element type int +> int[] array1 = ...; +> int element1 = array1[0]; +> +> // Rank 2, element type int +> int[,] array2 = ...; +> int element2 = array2[0, 1]; +> +> // Rank 1, element type int? (Nullable) +> int?[] array3 = ...; +> int? element3 = array3[0]; +> +> // Rank 1, element type string? (nullable string) +> string?[] array4 = ...; +> string? element4 = array4[0]; +> +> // Rank 1, element type string[,,][,] +> string[][,,][,] array5 = ...; +> string[,,][,] element5 = array5[0]; +> +> // Rank 1, element type string; the array itself is nullable +> string[]? array6 = ...; +> string element6 = array6?[0] ?? ""; +> +> // Rank 1, element type string[,]? +> string[,]?[] array7 = ...; +> string[,]? element7 = array7[0]; +> +> // Rank 3, element type int[]?[,] +> int[]?[,,][,] array8 = ...; +> int[]?[,] element8 = array8[0, 1, 2]; +> +> // Rank 1, element type string[,]?[]?[,,] +> string[,]?[]?[][,,] array9 = ...; +> string[,]?[]?[,,] element9 = array9[0]; +> +> // Rank 2, element type string[][][,,] +> // Note that this appears the same as the array9 example above other +> // than for the use of ? but the rank and element type are significantly different. +> string[,][][][,,] array10 = ...; +> string[][][,,] element10 = array10[0, 1]; +> ``` At run-time, a value of an array type can be `null` or a reference to an instance of that array type. diff --git a/standard/types.md b/standard/types.md index 865f99dfb..0eaaae6ce 100644 --- a/standard/types.md +++ b/standard/types.md @@ -54,17 +54,22 @@ interface_type ; array_type + : array_type nullable_type_annotation rank_specifier+ : non_array_type rank_specifier+ ; non_array_type + : non_array_non_nullable_type nullable_type_annotation? + | pointer_type // unsafe code support + ; + +non_array_non_nullable_type : value_type | class_type | interface_type | delegate_type | 'dynamic' | type_parameter - | pointer_type // unsafe code support ; rank_specifier @@ -732,7 +737,9 @@ There are two forms of nullability for reference types: > *Note:* The types `R` and `R?` are represented by the same underlying type, `R`. A variable of that underlying type can either contain a reference to an object or be the value `null`, which indicates “no reference.” *end note* -The syntactic distinction between a *nullable reference type* and its corresponding *non-nullable reference type* enables a compiler to generate diagnostics. A compiler must allow the *nullable_type_annotation* as defined in [§8.2.1](types.md#821-general). The diagnostics must be limited to warnings. Neither the presence or absence of nullable annotations, nor the state of the nullable context can change the compile time or runtime behavior of a program except for changes in any diagnostic messages generated at compile time. +The syntactic distinction between a *nullable reference type* and its corresponding *non-nullable reference type* enables a compiler to generate diagnostics. A compiler must allow the *nullable_type_annotation* as defined in [§8.2.1](types.md#821-general). The diagnostics must be limited to warnings. Other than in the meaning of array types, neither the presence or absence of nullable annotations, nor the state of the nullable context can change the compile time or runtime behavior of a program except for changes in any diagnostic messages generated at compile time. + +The meaning of array types is significantly impacted by the presence of *nullable_type_annotation* within an *array_type*, as described in [§17.2.1](arrays.md#1721-general). ### 8.9.2 Non-nullable reference types @@ -756,6 +763,8 @@ Throughout this specification, all C# code that does not contain nullable direct > *Note:* A nullable context where both flags are disabled matches the previous standard behavior for reference types. *end note* +The rank and element of an array type declared using *nullable_type_annotation* is not affected by the nullable context. + #### 8.9.4.2 Nullable disable When both the warning and annotations flags are disabled, the nullable context is *disabled*. @@ -1106,6 +1115,8 @@ A compiler may issue a warning when nullability annotations differ between two t A compiler may follow rules for interface variance ([§19.2.3.3](interfaces.md#19233-variance-conversion)), delegate variance ([§21.4](delegates.md#214-delegate-compatibility)), and array covariance ([§17.6](arrays.md#176-array-covariance)) in determining whether to issue a warning for type conversions. +(See [§17.2.1](arrays.md#1721-general) for the specification of the corresponding non-nullable array type used in `M7` and `M8`.) + > > ```csharp > #nullable enable @@ -1143,6 +1154,17 @@ A compiler may follow rules for interface variance ([§19.2.3.3](interfaces.md#1 > string[] v1 = p; // Warning > string[] v2 = p!; // No warning > } +> +> public void M7(string[][,] p) +> { +> string[,]?[] v1 = p; // No warning +> } +> +> public void M8(string[]?[,] p) +> { +> string[,][] v1 = p; // Warning +> string[,][] v2 = p!; // No warning +> } > } > ``` > From 0f04b055f84ec8f79950dd886f727261a77ea60b Mon Sep 17 00:00:00 2001 From: Jon Skeet Date: Tue, 27 Jan 2026 09:25:52 +0000 Subject: [PATCH 2/8] Fix grammar --- standard/types.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/standard/types.md b/standard/types.md index 0eaaae6ce..b07c41029 100644 --- a/standard/types.md +++ b/standard/types.md @@ -55,7 +55,7 @@ interface_type array_type : array_type nullable_type_annotation rank_specifier+ - : non_array_type rank_specifier+ + | non_array_type rank_specifier+ ; non_array_type From 6b932bc30592647e7a5f16125b8dcfb85ddc5b33 Mon Sep 17 00:00:00 2001 From: Jon Skeet Date: Wed, 11 Feb 2026 20:50:54 +0000 Subject: [PATCH 3/8] Update standard/types.md --- standard/types.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/standard/types.md b/standard/types.md index b07c41029..9d1c6ad3a 100644 --- a/standard/types.md +++ b/standard/types.md @@ -763,7 +763,7 @@ Throughout this specification, all C# code that does not contain nullable direct > *Note:* A nullable context where both flags are disabled matches the previous standard behavior for reference types. *end note* -The rank and element of an array type declared using *nullable_type_annotation* is not affected by the nullable context. +The rank and element of an array type declared using *nullable_type_annotation* is not affected by the nullable context ([§8.9.4](types.md#894-nullable-context)). #### 8.9.4.2 Nullable disable From 703098d919611994f40be3fd08d8fa9d610fcae6 Mon Sep 17 00:00:00 2001 From: Jon Skeet Date: Wed, 11 Feb 2026 20:56:45 +0000 Subject: [PATCH 4/8] Apply suggestion from @Nigel-Ecma Co-authored-by: Nigel-Ecma <6654683+Nigel-Ecma@users.noreply.github.com> --- standard/arrays.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/standard/arrays.md b/standard/arrays.md index 9934eb7b3..a19026f5f 100644 --- a/standard/arrays.md +++ b/standard/arrays.md @@ -31,7 +31,7 @@ Every array type is a reference type ([§8.2](types.md#82-reference-types)). The The grammar productions for array types are provided in [§8.2.1](types.md#821-general). -An array type is written as a *non_array_type* followed by one or more *rank_specifier*s, or an *array_type* followed by a *nullable_type_annotation* followed by one or more *rank_specifier*s. The latter production is used to represent array types where the element type is includes a nullable array type. +An array type is written as a *non_array_type* followed by one or more *rank_specifier*s, or an *array_type* followed by a *nullable_type_annotation* followed by one or more *rank_specifier*s. The latter production is used to represent array types where the element type is a nullable array type. A *non_array_type* is any *type* that is not itself an *array_type*. From 7262c39a979b0b0dc21523c57f45776bf4dec073 Mon Sep 17 00:00:00 2001 From: Jon Skeet Date: Wed, 11 Feb 2026 20:59:56 +0000 Subject: [PATCH 5/8] Apply suggestion from @jskeet --- standard/arrays.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/standard/arrays.md b/standard/arrays.md index a19026f5f..f3c05e6dd 100644 --- a/standard/arrays.md +++ b/standard/arrays.md @@ -35,7 +35,7 @@ An array type is written as a *non_array_type* followed by one or more *rank_spe A *non_array_type* is any *type* that is not itself an *array_type*. -When determining the rank and element type of array type as specified below, only the *rank_specifier*s in the top-most production are considered, so in the production `array_type nullable_type_annotation rank_specifier+`, any `rank_specifier` within the `array_type` is ignored. +When determining the rank and element type of array type as specified below, only the *rank_specifier*s in the outer-most production are considered, so in the production `array_type nullable_type_annotation rank_specifier+`, any `rank_specifier` within the `array_type` is ignored. The rank of an array type is given by the leftmost *rank_specifier* in the *array_type*: A *rank_specifier* indicates that the array is an array with a rank of one plus the number of “`,`” tokens in the *rank_specifier*. From 5b029a1691d3fb7ddc606b6b400ce159af78d677 Mon Sep 17 00:00:00 2001 From: Jon Skeet Date: Wed, 11 Feb 2026 21:00:28 +0000 Subject: [PATCH 6/8] Apply suggestion from @jskeet --- standard/arrays.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/standard/arrays.md b/standard/arrays.md index f3c05e6dd..65a197a6b 100644 --- a/standard/arrays.md +++ b/standard/arrays.md @@ -31,7 +31,7 @@ Every array type is a reference type ([§8.2](types.md#82-reference-types)). The The grammar productions for array types are provided in [§8.2.1](types.md#821-general). -An array type is written as a *non_array_type* followed by one or more *rank_specifier*s, or an *array_type* followed by a *nullable_type_annotation* followed by one or more *rank_specifier*s. The latter production is used to represent array types where the element type is a nullable array type. +An array type is written as a *non_array_type* followed by one or more *rank_specifier*s, or an *array_type* followed by a *nullable_type_annotation* followed by one or more *rank_specifier*s. The latter production is used to represent array types where the element type is an array type which is nullable ([§8.9](types.md#89-reference-types-and-nullability)). A *non_array_type* is any *type* that is not itself an *array_type*. From 228bb5a8125be40659dcbadd73c6aae2a79a6147 Mon Sep 17 00:00:00 2001 From: Jon Skeet Date: Wed, 11 Feb 2026 21:06:07 +0000 Subject: [PATCH 7/8] Apply suggestion from @jskeet --- standard/arrays.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/standard/arrays.md b/standard/arrays.md index 65a197a6b..ce5375cf0 100644 --- a/standard/arrays.md +++ b/standard/arrays.md @@ -41,8 +41,6 @@ The rank of an array type is given by the leftmost *rank_specifier* in the *arra The element type of an array type is the type that results from deleting the leftmost *rank_specifier*. -In effect, the *rank_specifier*s are read from left to right *before* the final non-array element type. - > *Example*: The following code shows several variable declarations, including a mixture of single-dimensional arrays, multi-dimensional arrays, and arrays of arrays, with some using nullable reference types. In each case, the rank and element type is described, and then demonstrated with a second variable declaration which is initialized using an element access expression. > > From 2257215ff7c9f674e5e71e478bf66fe9702e560c Mon Sep 17 00:00:00 2001 From: Jon Skeet Date: Wed, 11 Feb 2026 21:11:53 +0000 Subject: [PATCH 8/8] Apply suggestion from @jskeet --- standard/arrays.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/standard/arrays.md b/standard/arrays.md index ce5375cf0..bd3fdb280 100644 --- a/standard/arrays.md +++ b/standard/arrays.md @@ -39,7 +39,7 @@ When determining the rank and element type of array type as specified below, onl The rank of an array type is given by the leftmost *rank_specifier* in the *array_type*: A *rank_specifier* indicates that the array is an array with a rank of one plus the number of “`,`” tokens in the *rank_specifier*. -The element type of an array type is the type that results from deleting the leftmost *rank_specifier*. +The element type of an array type is the type that results from deleting the leftmost *rank_specifier* from the *array_type*. > *Example*: The following code shows several variable declarations, including a mixture of single-dimensional arrays, multi-dimensional arrays, and arrays of arrays, with some using nullable reference types. In each case, the rank and element type is described, and then demonstrated with a second variable declaration which is initialized using an element access expression. >