From d3acc5b097d56e18e06d556d3bd7fe7fd2c42f08 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Wed, 17 Jul 2024 09:48:31 -0400 Subject: [PATCH] Update language reference syntax. clarify source and binary break. Turn on preview --- .../language-reference/attributes/general.md | 26 +++++++++- .../builtin-types/ref-struct.md | 28 +++++++++-- .../language-reference/keywords/index.md | 9 ++-- .../snippets/GenericWhereConstraints.cs | 11 +++++ .../keywords/where-generic-type-constraint.md | 9 +++- .../constraints-on-type-parameters.md | 49 ++++++++++++++++++- .../generics/snippets/generics.csproj | 3 +- 7 files changed, 123 insertions(+), 12 deletions(-) diff --git a/docs/csharp/language-reference/attributes/general.md b/docs/csharp/language-reference/attributes/general.md index dfa5c5548454a..905babc2ff31a 100644 --- a/docs/csharp/language-reference/attributes/general.md +++ b/docs/csharp/language-reference/attributes/general.md @@ -5,7 +5,19 @@ description: "Learn about attributes that affect code generated by the compiler: --- # Miscellaneous attributes interpreted by the C# compiler -The attributes `Conditional`, `Obsolete`, `AttributeUsage`, `AsyncMethodBuilder`, `InterpolatedStringHandler`, `ModuleInitializer`, and `Experimental` can be applied to elements in your code. They add semantic meaning to those elements. The compiler uses those semantic meanings to alter its output and report possible mistakes by developers using your code. +There are several attributes that can be applied to elements in your code that add semantic meaning to those elements: + +- [`Conditional`](#conditional-attribute): Make execution of a method dependent on a preprocessor identifier. +- [`Obsolete`](#obsolete-attribute): Mark a type or member for (potential) future removal. +- [`AttributeUsage`](#attributeusage-attribute): Declare the language elements where an attribute can be applied. +- [`AsyncMethodBuilder`](#asyncmethodbuilder-attribute): Declare an async method builder type. +- [`InterpolatedStringHandler`](#interpolatedstringhandler-and-interpolatedstringhandlerarguments-attributes): Define an interpolated string builder for a known scenario. +- [`ModuleInitializer`](#moduleinitializer-attribute): Declare a method that initializes a module. +- [`SkipLocalsInit`](#skiplocalsinit-attribute): Elide the code that initializes local variable storage to 0. +- [`UnscopedRef`](#unscopedref-attribute): Declare that a `ref` variable normally interpreted as `scoped` should be treated as unscoped. +- [`Experimental`](#experimental-attribute): Mark a type or member as experimental. + +The compiler uses those semantic meanings to alter its output and report possible mistakes by developers using your code. ## `Conditional` attribute @@ -205,6 +217,18 @@ To try this code yourself, set the `AllowUnsafeBlocks` compiler option in your * ``` +## `UnscopedRef` attribute + +The `UnscopedRef` attribute marks a variable declaration as unscoped, meaning the reference is allowed to escape. + +You add this attribute where the compiler treats a `ref` as implicitly `scoped`: + +- The `this` parameter for `struct` instance methods. +- `ref` parameters that refer to `ref struct` types. +- `out` parameters. + +Applying the marks the element as unscoped. + ## See also - diff --git a/docs/csharp/language-reference/builtin-types/ref-struct.md b/docs/csharp/language-reference/builtin-types/ref-struct.md index cb547291204f1..09d2032991f27 100644 --- a/docs/csharp/language-reference/builtin-types/ref-struct.md +++ b/docs/csharp/language-reference/builtin-types/ref-struct.md @@ -9,14 +9,12 @@ You can use the `ref` modifier in the declaration of a [structure type](struct.m - A `ref struct` can't be the element type of an array. - A `ref struct` can't be a declared type of a field of a class or a non-`ref struct`. -- A `ref struct` can't implement interfaces. - A `ref struct` can't be boxed to or . -- A `ref struct` can't be a type argument. - A `ref struct` variable can't be captured in a [lambda expression](../operators/lambda-expressions.md) or a [local function](../../programming-guide/classes-and-structs/local-functions.md). - Before C# 13,`ref struct` variables can't be used in an `async` method. Beginning with C# 13, a `ref struct` variable can't be used in the same block as the [`await`](../operators/await.md) expression in an [`async`](../keywords/async.md) method. However, you can use `ref struct` variables in synchronous methods, for example, in methods that return or . - Before C# 13, a `ref struct` variable can't be used in [iterators](../../iterators.md). Beginning with C# 13, `ref struct` types and `ref` locals can be used in iterators, provided they aren't in code segments with the `yield return` statement. - -You can define a disposable `ref struct`. To do that, ensure that a `ref struct` fits the [disposable pattern](~/_csharplang/proposals/csharp-8.0/using.md#pattern-based-using). That is, it has an instance `Dispose` method, which is accessible, parameterless and has a `void` return type. You can use the [using statement or declaration](../statements/using.md) with an instance of a disposable `ref struct`. +- Before C# 13, a `ref struct` can't implement interfaces. Beginning with C# 13, a `ref` struct can implement interfaces, but must adhere to the [ref safety](~/_csharpstandard/standard/structs.md#1623-ref-modifier) rules. For example, a `ref struct` type can't be converted to the interface type because that requires a boxing conversion. +- Before C# 13, a `ref struct` can't be a type argument. Beginning with C# 13, a `ref struct` can be the type argument when the type parameter specifies the `allows ref struct` in its `where` clause. Typically, you define a `ref struct` type when you need a type that also includes data members of `ref struct` types: @@ -58,6 +56,28 @@ public readonly ref struct Span The `Span` type stores a reference through which it accesses the contiguous elements in memory. The use of a reference enables a `Span` instance to avoid copying the storage it refers to. +# The disposable pattern + +You can define a disposable `ref struct`. To do that, ensure that a `ref struct` fits the [disposable pattern](~/_csharplang/proposals/csharp-8.0/using.md#pattern-based-using). That is, it has an instance `Dispose` method, which is accessible, parameterless and has a `void` return type. You can use the [using statement or declaration](../statements/using.md) with an instance of a disposable `ref struct`. + +Beginning with C# 13, you can also implement the on `ref struct` types. However, overload resolution prefers the disposable pattern to the interface method. Only if a suitable `Dispose` method isn't found will an `IDisposable.Dispose` method be chosen. + +## Restrictions for `ref struct` types that implement an interface + +These restrictions ensure that a `ref struct` type that implements an interface obeys the necessary [ref safety](~/_csharpstandard/standard/structs.md#1623-ref-modifier) rules. + +- A `ref struct` can't be converted to an instance of an interface it implements. This includes the implicit conversion when you use a `ref struct` type as an argument when the parameter is an interface type. The conversion results in a boxing conversion, which violates ref safety. +- A `ref struct` that implements an interface *must* implement all interface members. The `ref struct` must implement members where the interface includes a default implementation. + +The compiler enforces these restrictions. If you write `ref struct` types that implement interfaces, each new update may include new [default interface members](../keywords/interface.md#default-interface-members). Until you provide an implementation for these new methods, your application won't compile. + +> [!IMPORTANT] +> A `ref struct` that implements an interface includes the potential for later source-breaking and binary-breaking changes. The break occurs if a `ref struct` implements an interface defined in another assembly, and that assembly provides an update which adds default members to that interface. +> +> The source-break happens when you recompile the `ref struct`: It must implement the new member, even though there is a default implementation. +> +> The binary-break happens if you upgrade the external assembly without recompiling the `ref struct` type *and* the updated code calls the default implementation of the new method. The runtime throws an exception when the default member is accessed. + ## C# language specification For more information, see the following sections of the [C# language specification](~/_csharpstandard/standard/README.md): diff --git a/docs/csharp/language-reference/keywords/index.md b/docs/csharp/language-reference/keywords/index.md index 6e7ed51432ddf..7b9596109bf54 100644 --- a/docs/csharp/language-reference/keywords/index.md +++ b/docs/csharp/language-reference/keywords/index.md @@ -112,8 +112,9 @@ A contextual keyword is used to provide a specific meaning in the code, but it i :::row::: :::column::: [`add`](add.md) - [`and`](../operators/patterns.md#logical-patterns) + [`allows`](where-generic-type-constraint.md) [`alias`](extern-alias.md) + [`and`](../operators/patterns.md#logical-patterns) [`ascending`](ascending.md) [`args`](../../fundamentals/program-structure/top-level-statements.md#args) [`async`](async.md) @@ -122,10 +123,10 @@ A contextual keyword is used to provide a specific meaning in the code, but it i [`descending`](descending.md) [`dynamic`](../builtin-types/reference-types.md) [`equals`](equals.md) - [`file`](file.md) - [`from`](from-clause.md) :::column-end::: :::column::: + [`file`](file.md) + [`from`](from-clause.md) [`get`](get.md) [`global`](../operators/namespace-alias-qualifier.md) [`group`](group-clause.md) @@ -136,9 +137,9 @@ A contextual keyword is used to provide a specific meaning in the code, but it i [`managed` (function pointer calling convention)](../unsafe-code.md#function-pointers) [`nameof`](../operators/nameof.md) [`nint`](../builtin-types/integral-numeric-types.md) - [`not`](../operators/patterns.md#logical-patterns) :::column-end::: :::column::: + [`not`](../operators/patterns.md#logical-patterns) [`notnull`](../../programming-guide/generics/constraints-on-type-parameters.md#notnull-constraint) [`nuint`](../builtin-types/integral-numeric-types.md) [`on`](on.md) diff --git a/docs/csharp/language-reference/keywords/snippets/GenericWhereConstraints.cs b/docs/csharp/language-reference/keywords/snippets/GenericWhereConstraints.cs index 73634507f559e..154d3d6cba2c0 100644 --- a/docs/csharp/language-reference/keywords/snippets/GenericWhereConstraints.cs +++ b/docs/csharp/language-reference/keywords/snippets/GenericWhereConstraints.cs @@ -49,6 +49,17 @@ class NotNullContainer } // + // + public class GenericRefStruct where T : allows ref struct + { + // Scoped is allowed because T might be a ref struct + public void M(scoped T parm) + { + + } + } + // + // public interface IMyInterface { } diff --git a/docs/csharp/language-reference/keywords/where-generic-type-constraint.md b/docs/csharp/language-reference/keywords/where-generic-type-constraint.md index 4cb72836a70d3..3ae395507f055 100644 --- a/docs/csharp/language-reference/keywords/where-generic-type-constraint.md +++ b/docs/csharp/language-reference/keywords/where-generic-type-constraint.md @@ -9,6 +9,7 @@ f1_keywords: - "classconstraint_CSharpKeyword" - "structconstraint_CSharpKeyword" - "enumconstraint_CSharpKeyword" + - "allows_CsharpKeyword" helpviewer_keywords: - "where (generic type constraint) [C#]" --- @@ -58,7 +59,13 @@ The `where` clause may also include a constructor constraint, `new()`. That cons :::code language="csharp" source="snippets/GenericWhereConstraints.cs" ID="Snippet5"::: -The `new()` constraint appears last in the `where` clause. The `new()` constraint can't be combined with the `struct` or `unmanaged` constraints. All types satisfying those constraints must have an accessible parameterless constructor, making the `new()` constraint redundant. +The `new()` constraint appears last in the `where` clause, unless it is followed by the `allows ref struct` anti-constraint. The `new()` constraint can't be combined with the `struct` or `unmanaged` constraints. All types satisfying those constraints must have an accessible parameterless constructor, making the `new()` constraint redundant. + +This anti-constraint declares that the type argument for `T` can be a `ref struct` type. For example: + +:::code language="csharp" source="snippets/GenericWhereConstraints.cs" ID="SnippetRefStruct"::: + +The generic type or method must obey ref safety rules for any instance of `T` because it might be a `ref struct`. The `allows ref struct` clause can't be combined with the `class` or `class?` constraint. The `allows ref struct` anti-constraint must follow all constraints for that type argument. With multiple type parameters, use one `where` clause for each type parameter, for example: diff --git a/docs/csharp/programming-guide/generics/constraints-on-type-parameters.md b/docs/csharp/programming-guide/generics/constraints-on-type-parameters.md index cb938180e6715..3b8ee5f48c8db 100644 --- a/docs/csharp/programming-guide/generics/constraints-on-type-parameters.md +++ b/docs/csharp/programming-guide/generics/constraints-on-type-parameters.md @@ -30,6 +30,7 @@ Constraints inform the compiler about the capabilities a type argument must have |`where T :` *\?*|The type argument must be or implement the specified interface. Multiple interface constraints can be specified. The constraining interface can also be generic. In a nullable context, `T` can be a nullable reference type, a non-nullable reference type, or a value type. `T` can't be a nullable value type.| |`where T : U`|The type argument supplied for `T` must be or derive from the argument supplied for `U`. In a nullable context, if `U` is a non-nullable reference type, `T` must be a non-nullable reference type. If `U` is a nullable reference type, `T` can be either nullable or non-nullable. | |`where T : default`|This constraint resolves the ambiguity when you need to specify an unconstrained type parameter when you override a method or provide an explicit interface implementation. The `default` constraint implies the base method without either the `class` or `struct` constraint. For more information, see the [`default` constraint](~/_csharplang/proposals/csharp-9.0/unconstrained-type-parameter-annotations.md#default-constraint) spec proposal.| +|`where T : allows ref struct`|This anti-constraint declares that the type argument for `T` can be a `ref struct` type. The generic type or method must obey ref safety rules for any instance of `T` because it might be a `ref struct`.| Some constraints are mutually exclusive, and some constraints must be in a specified order: @@ -37,8 +38,10 @@ Some constraints are mutually exclusive, and some constraints must be in a speci - The base class constraint, (`where T : Base` or `where T : Base?`), can't be combined with any of the constraints `struct`, `class`, `class?`, `notnull`, or `unmanaged`. - You can apply at most one base class constraint, in either form. If you want to support the nullable base type, use `Base?`. - You can't name both the non-nullable and nullable form of an interface as a constraint. -- The `new()` constraint can't be combined with the `struct` or `unmanaged` constraint. If you specify the `new()` constraint, it must be the last constraint for that type parameter. +- The `new()` constraint can't be combined with the `struct` or `unmanaged` constraint. If you specify the `new()` constraint, it must be the last constraint for that type parameter. Anti-constraints, if applicable, can follow the `new()` constraint. - The `default` constraint can be applied only on override or explicit interface implementations. It can't be combined with either the `struct` or `class` constraints. +- The `allows ref struct` clause can't be combined with the `class` or `class?` constraint. +- The `allows ref struct` anti-constraint must follow all constraints for that type parameter. ## Why use constraints @@ -161,6 +164,50 @@ public interface IAdditionSubtraction where T : IAdditionSubtraction The preceding syntax would require implementers to use [explicit interface implementation](../interfaces/explicit-interface-implementation.md) for those methods. Providing the extra constraint enables the interface to define the operators in terms of the type parameters. Types that implement the interface can implicitly implement the interface methods. +## Allows ref struct + +The `allows ref struct` anti-constraint declares that the corresponding type argument can be a [`ref struct`](../../language-reference/builtin-types/ref-struct.md) type. Instances of that type parameter must obey the following rules: + +- It can't be boxed. +- It participates in [ref safety rules](~/_csharpstandard/standard/structs.md#16412-safe-context-constraint). +- Instances can't be used where a `ref struct` type isn't allowed, such as `static` fields. +- Instances can be marked with the `scoped` modifier. + +The `allows ref struct` clause isn't inherited. In the following code: + +```csharp +class C + where T : allows ref struct + where S : T +{ + // etc +} +``` + +The argument for `S` can't be a `ref struct` because `S` doesn't have the `allows ref struct` clause. + +A type parameter that has the `allows ref struct` clause can't be used as a type argument unless the corresponding type parameter also has the `allows ref struct` clause. This rule is demonstrated in the following example: + +```csharp +public class Allow where T : allows ref struct +{ + +} + +public class Disallow +{ +} + +public class Example where T : allows ref struct +{ + private Allow fieldOne; // Allowed. T is allowed to be a ref struct + + private Disallow fieldTwo; // Error. T is not allowed to be a ref struct +} +``` + +The preceding sample shows that a type argument that might be a `ref struct` type can't be substituted for a type parameter that can't be a `ref struct` type. + ## See also - diff --git a/docs/csharp/programming-guide/generics/snippets/generics.csproj b/docs/csharp/programming-guide/generics/snippets/generics.csproj index 9d40194d4ea68..4a35461bf67e1 100644 --- a/docs/csharp/programming-guide/generics/snippets/generics.csproj +++ b/docs/csharp/programming-guide/generics/snippets/generics.csproj @@ -2,9 +2,10 @@ Exe - net8.0 + net9.0 enable enable true + preview