From b733b4ac0566aed6e34e21d39d53d7515a1efaa2 Mon Sep 17 00:00:00 2001 From: Rex Jaeschke Date: Fri, 9 May 2025 12:53:10 -0400 Subject: [PATCH 1/2] Update classes.md --- standard/classes.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/standard/classes.md b/standard/classes.md index b02654945..7c1751148 100644 --- a/standard/classes.md +++ b/standard/classes.md @@ -409,6 +409,7 @@ primary_constraint | 'struct' | 'notnull' | 'unmanaged' + | 'default' ; secondary_constraint @@ -429,7 +430,9 @@ Each *type_parameter_constraints_clause* consists of the token `where`, followed The list of constraints given in a `where` clause can include any of the following components, in this order: a single primary constraint, one or more secondary constraints, and the constructor constraint, `new()`. -A primary constraint can be a class type, the ***reference type constraint*** `class`, the ***value type constraint*** `struct`, the ***not null constraint*** `notnull` or the ***unmanaged type constraint*** `unmanaged`. The class type and the reference type constraint can include the *nullable_type_annotation*. +A primary constraint can be a class type, the ***reference type constraint*** `class`, the ***value type constraint*** `struct`, the ***not null constraint*** `notnull`, the ***unmanaged type constraint*** `unmanaged`, or `default`. The class type and the reference type constraint can include the *nullable_type_annotation*. + +It is a compile-time error to use a `default` constraint other than on a method override or explicit implementation. It is a compile-time error to use a `default` constraint when the corresponding type parameter in the overridden or interface method is constrained to a reference type or value type. A secondary constraint can be an *interface_type* or *type_parameter*, optionally followed by a *nullable_type_annotation*. The presence of the *nullable_type_annotation* indicates that the type argument is allowed to be the nullable reference type that corresponds to a non-nullable reference type that satisfies the constraint. @@ -533,6 +536,8 @@ The unmanaged type constraint specifies that a type argument used for the type p Because `unmanaged` is not a keyword, in *primary_constraint* the unmanaged constraint is always syntactically ambiguous with *class_type*. For compatibility reasons, if a name lookup ([§12.8.4](expressions.md#1284-simple-names)) of the name `unmanaged` succeeds it is treated as a `class_type`. Otherwise it is treated as the unmanaged constraint. +The `default` constraint allows a *nullable_type_annotation* on a type parameter that is not constrained to reference types or value types. + Pointer types are never allowed to be type arguments, and don’t satisfy any type constraints, even unmanaged, despite being unmanaged types. If a constraint is a class type, an interface type, or a type parameter, that type specifies a minimal “base type” that every type argument used for that type parameter shall support. Whenever a constructed type or generic method is used, the type argument is checked against the constraints on the type parameter at compile-time. The type argument supplied shall satisfy the conditions described in [§8.4.5](types.md#845-satisfying-constraints). From 1fbcb02d1c2d33dd497be6332119cd99e5ab495c Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Wed, 25 Feb 2026 13:16:16 -0500 Subject: [PATCH 2/2] Review and update text. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Act on existing TODO markers Remove the restriction in classes.md: "The nullable type annotation, ?, can only be used on a type parameter that has the value type constraint, the reference type constraint without the nullable_type_annotation, or a class type constraint without the nullable_type_annotation." Replace it with the C# 9 rule from the feature spec: unless a type parameter is explicitly constrained to value types, ? annotations can only be applied within a #nullable enable context. Uncomment and integrate classes.md: "For a type parameter T when the type argument is a nullable reference type C?, instances of T? are interpreted as C?, not C??." (currently inside a comment). — Override/explicit implementation constraint list In classes.md, the text says: "Such declarations may only have type_parameter_constraints_clauses containing the primary_constraints class and struct…" Add default to this list (e.g., "…the primary_constraints class, struct, and default…") and reference the new §15.6.5 and §19.6.2 meaning. — Override method T? interpretation rules In classes.md, the current rule only covers class and struct constraints and describes how T? is interpreted in overriding signatures. Update to add the default case: If a default constraint is added for type parameter T, then T? represents the annotated type — a nullable reference type when T is a reference type, or just T when T is a value type (matching the feature spec behavior; no U??). Consider adding an example analogous to the feature spec's A2/B2 example showing where T : default on an override of an unconstrained method. — Explicit interface implementation T? interpretation rules In interfaces.md, same situation as §15.6.5: update to add the default constraint case and its T? interpretation. The text ("Without the type parameter constraint where T : class, the base method with the reference-typed type parameter cannot be overridden.") should be updated or supplemented with an example using where T : default for an unconstrained interface method. --- standard/classes.md | 37 +++++++++++++++++++++++++++++-------- standard/interfaces.md | 29 ++++++++++++++++++++++++++--- 2 files changed, 55 insertions(+), 11 deletions(-) diff --git a/standard/classes.md b/standard/classes.md index 7c1751148..e8dc8a874 100644 --- a/standard/classes.md +++ b/standard/classes.md @@ -447,11 +447,9 @@ The nullability of the type argument need not match the nullability of the type > *Note*: To specify that a type argument is a nullable reference type, don’t add the nullable type annotation as a constraint (use `T : class` or `T : BaseClass`), but use `T?` throughout the generic declaration to indicate the corresponding nullable reference type for the type argument. *end note* - -The nullable type annotation, `?`, can only be used on a type parameter that has the value type constraint, the reference type constraint without the *nullable_type_annotation*, or a class type constraint without the *nullable_type_annotation*. +Unless a type parameter is explicitly constrained to value types, the nullable type annotation `?` can only be applied to a type parameter within a `#nullable enable` context. - - +For a type parameter `T` when the type argument is a nullable reference type `C?`, instances of `T?` are interpreted as `C?`, not `C??`. > *Example*: The following examples show how the nullability of a type argument impacts the nullability of a declaration of its type parameter: > @@ -2169,7 +2167,7 @@ The *ref_return_type* of a returns-by-ref method declaration specifies the type A generic method is a method whose declaration includes a *type_parameter_list*. This specifies the type parameters for the method. The optional *type_parameter_constraints_clause*s specify the constraints for the type parameters. -A generic *method_declaration*, either with an `override` modifier, or for an explicit interface member implementation, inherits type parameter constraints from the overridden method or interface member respectively. Such declarations may only have *type_parameter_constraints_clause*s containing the *primary_constraint*s `class` and `struct`, the meaning of which in this context is defined in [§15.6.5](classes.md#1565-override-methods) and [§19.6.2](interfaces.md#1962-explicit-interface-member-implementations) for overriding methods and explicit interface implementations respectively. +A generic *method_declaration*, either with an `override` modifier, or for an explicit interface member implementation, inherits type parameter constraints from the overridden method or interface member respectively. Such declarations may only have *type_parameter_constraints_clause*s containing the *primary_constraint*s `class`, `struct`, and `default`, the meaning of which in this context is defined in [§15.6.5](classes.md#1565-override-methods) and [§19.6.2](interfaces.md#1962-explicit-interface-member-implementations) for overriding methods and explicit interface implementations respectively. The *member_name* specifies the name of the method. Unless the method is an explicit interface member implementation ([§19.6.2](interfaces.md#1962-explicit-interface-member-implementations)), the *member_name* is simply an *identifier*. @@ -2761,9 +2759,10 @@ A compile-time error occurs unless all of the following are true for an override - The overridden base method is not a sealed method. - There is an identity conversion between the return type of the overridden base method and the override method. - The override declaration and the overridden base method have the same declared accessibility. In other words, an override declaration cannot change the accessibility of the virtual method. However, if the overridden base method 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. -- A *type_parameter_constraints_clause* may only consist of the `class` or `struct` *primary_constraint*s applied to *type_parameter*s which are known according to the inherited constraints to be either reference or value types respectively. Any type of the form `T?` in the overriding method’s signature, where `T` is a type parameter, is interpreted as follows: - - If a `class` constraint is added for type parameter `T` then `T?` is a nullable reference type; otherwise - - If either there is no added constraint, or a `struct` constraint is added, for the type parameter `T` then `T?` is a nullable value type. +- A *type_parameter_constraints_clause* may only consist of the `class`, `struct`, or `default` *primary_constraint*s. The `class` and `struct` constraints are applied to *type_parameter*s which are known according to the inherited constraints to be either reference or value types respectively. The `default` constraint is applied to *type_parameter*s that are not constrained to either reference or value types. Any type of the form `T?` in the overriding method’s signature, where `T` is a type parameter, is interpreted as follows: + - If a `class` constraint is added for type parameter `T` then `T?` is a nullable reference type. + - If either a `struct` constraint is added, or no constraint is added and the inherited constraint is a value type constraint, for the type parameter `T` then `T?` is a nullable value type. + - If a `default` constraint is added for type parameter `T` then `T?` represents a nullable instance of the corresponding reference type when `T` is a reference type, and an instance of `T` when `T` is a value type. If `T` is substituted with an annotated type `U?`, then `T?` represents `U?`, not `U??`. > *Example*: The following demonstrates how the overriding rules work for generic classes: > @@ -2813,6 +2812,28 @@ A compile-time error occurs unless all of the following are true for an override > ``` > > Without the type parameter constraint `where T : class`, the base method with the reference-typed type parameter cannot be overridden. *end example* + + + +> *Example*: The following demonstrates how the `default` constraint works when overriding a method with an unconstrained type parameter: +> +> +> ```csharp +> #nullable enable +> class A2 +> { +> public virtual void F2(T? t) where T : struct { } +> public virtual void F2(T? t) { } +> } +> +> class B2 : A2 +> { +> public override void F2(T? t) /*where T : struct*/ { } +> public override void F2(T? t) where T : default { } +> } +> ``` +> +> The `default` constraint on `B2.F2` is required to override the unconstrained `A2.F2` with a `T?` parameter. Without it, `T?` would be interpreted as a nullable value type. *end example* An override declaration can access the overridden base method using a *base_access* ([§12.8.15](expressions.md#12815-base-access)). diff --git a/standard/interfaces.md b/standard/interfaces.md index 5ed62239f..4409495d5 100644 --- a/standard/interfaces.md +++ b/standard/interfaces.md @@ -855,10 +855,11 @@ It is a compile-time error for an explicit interface member implementation to in An explicit interface method implementation inherits any type parameter constraints from the interface. -A *type_parameter_constraints_clause* on an explicit interface method implementation may only consist of the `class` or `struct` *primary_constraint*s applied to *type_parameter*s which are known according to the inherited constraints to be either reference or value types respectively. Any type of the form `T?` in the signature of the explicit interface method implementation, where `T` is a type parameter, is interpreted as follows: +A *type_parameter_constraints_clause* on an explicit interface method implementation may only consist of the `class`, `struct`, or `default` *primary_constraint*s. The `class` and `struct` constraints are applied to *type_parameter*s which are known according to the inherited constraints to be either reference or value types respectively. The `default` constraint is applied to *type_parameter*s that are not constrained to either reference or value types. Any type of the form `T?` in the signature of the explicit interface method implementation, where `T` is a type parameter, is interpreted as follows: -- If a `class` constraint is added for type parameter `T` then `T?` is a nullable reference type; otherwise -- If either there is no added constraint, or a `struct` constraint is added, for the type parameter `T` then `T?` is a nullable value type. +- If a `class` constraint is added for type parameter `T` then `T?` is a nullable reference type. +- If either a `struct` constraint is added, or no constraint is added and the inherited constraint is a value type constraint, for the type parameter `T` then `T?` is a nullable value type. +- If a `default` constraint is added for type parameter `T` then `T?` represents a nullable instance of the corresponding reference type when `T` is a reference type, and an instance of `T` when `T` is a value type. If `T` is substituted with an annotated type `U?`, then `T?` represents `U?`, not `U??`. > *Example*: The following demonstrates how the rules work when type parameters are involved: > @@ -881,6 +882,28 @@ A *type_parameter_constraints_clause* on an explicit interface method implementa > Without the type parameter constraint `where T : class`, the base method with the reference-typed type parameter cannot be overridden. *end example* + +> *Example*: The following demonstrates how the `default` constraint works when explicitly implementing an interface method with an unconstrained type parameter: +> +> +> ```csharp +> #nullable enable +> interface I2 +> { +> void F2(T? t) where T : struct; +> void F2(T? t); +> } +> +> class C2 : I2 +> { +> void I2.F2(T? t) /*where T : struct*/ { } +> void I2.F2(T? t) where T : default { } +> } +> ``` +> +> The `default` constraint on `C2.F2` is required to implement the unconstrained `I2.F2` with a `T?` parameter. Without it, `T?` would be interpreted as a nullable value type. *end example* + + > *Note*: Explicit interface member implementations have different accessibility characteristics than other members. Because explicit interface member implementations are never accessible through a qualified interface member name in a method invocation or a property access, they are in a sense private. However, since they can be accessed through the interface, they are in a sense also as public as the interface in which they are declared. > Explicit interface member implementations serve two primary purposes: