Skip to content

Commit

Permalink
Update language reference syntax.
Browse files Browse the repository at this point in the history
clarify source and binary break.

Turn on preview
  • Loading branch information
BillWagner committed Jul 23, 2024
1 parent f5856e6 commit d3acc5b
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 12 deletions.
26 changes: 25 additions & 1 deletion docs/csharp/language-reference/attributes/general.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -205,6 +217,18 @@ To try this code yourself, set the `AllowUnsafeBlocks` compiler option in your *
</PropertyGroup>
```

## `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 <xref:System.Runtime.CompilerServices.UnscopedRef?displayProperty=nameWithType> marks the element as unscoped.

## See also

- <xref:System.Attribute>
Expand Down
28 changes: 24 additions & 4 deletions docs/csharp/language-reference/builtin-types/ref-struct.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <xref:System.ValueType?displayProperty=nameWithType> or <xref:System.Object?displayProperty=nameWithType>.
- 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 <xref:System.Threading.Tasks.Task> or <xref:System.Threading.Tasks.Task%601>.
- 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:

Expand Down Expand Up @@ -58,6 +56,28 @@ public readonly ref struct Span<T>

The `Span<T>` type stores a reference through which it accesses the contiguous elements in memory. The use of a reference enables a `Span<T>` 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 <xref:System.IDisposable?displayName=nameWithType> 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):
Expand Down
9 changes: 5 additions & 4 deletions docs/csharp/language-reference/keywords/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,17 @@ class NotNullContainer<T>
}
// </Snippet5>

// <SnippetRefStruct>
public class GenericRefStruct<T> where T : allows ref struct

Check failure on line 53 in docs/csharp/language-reference/keywords/snippets/GenericWhereConstraints.cs

View workflow job for this annotation

GitHub Actions / snippets-build

D:\a\docs\docs\docs\csharp\language-reference\keywords\snippets\GenericWhereConstraints.cs(53,55): error CS8652: The feature 'allows ref struct constraint' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. [D:\a\docs\docs\docs\csharp\language-reference\keywords\snippets\keywords.csproj]

Check failure on line 53 in docs/csharp/language-reference/keywords/snippets/GenericWhereConstraints.cs

View workflow job for this annotation

GitHub Actions / snippets-build

D:\a\docs\docs\docs\csharp\language-reference\keywords\snippets\GenericWhereConstraints.cs(53,55): error CS9240: Target runtime doesn't support by-ref-like generics. [D:\a\docs\docs\docs\csharp\language-reference\keywords\snippets\keywords.csproj]

Check failure on line 53 in docs/csharp/language-reference/keywords/snippets/GenericWhereConstraints.cs

View workflow job for this annotation

GitHub Actions / snippets-build

D:\a\docs\docs\docs\csharp\language-reference\keywords\snippets\GenericWhereConstraints.cs(53,55): error CS8652: The feature 'allows ref struct constraint' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. [D:\a\docs\docs\docs\csharp\language-reference\keywords\snippets\keywords.csproj]

Check failure on line 53 in docs/csharp/language-reference/keywords/snippets/GenericWhereConstraints.cs

View workflow job for this annotation

GitHub Actions / snippets-build

D:\a\docs\docs\docs\csharp\language-reference\keywords\snippets\GenericWhereConstraints.cs(53,55): error CS9240: Target runtime doesn't support by-ref-like generics. [D:\a\docs\docs\docs\csharp\language-reference\keywords\snippets\keywords.csproj]
{
// Scoped is allowed because T might be a ref struct
public void M(scoped T parm)
{

}
}
// </SnippetRefStruct>

// <Snippet6>
public interface IMyInterface { }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ f1_keywords:
- "classconstraint_CSharpKeyword"
- "structconstraint_CSharpKeyword"
- "enumconstraint_CSharpKeyword"
- "allows_CsharpKeyword"
helpviewer_keywords:
- "where (generic type constraint) [C#]"
---
Expand Down Expand Up @@ -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:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,18 @@ Constraints inform the compiler about the capabilities a type argument must have
|`where T :` *\<interface name>?*|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:

- You can apply at most one of the `struct`, `class`, `class?`, `notnull`, and `unmanaged` constraints. If you supply any of these constraints, it must be the first constraint specified for that type parameter.
- 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

Expand Down Expand Up @@ -161,6 +164,50 @@ public interface IAdditionSubtraction<T> where T : IAdditionSubtraction<T>

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<T, S>
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<T> where T : allows ref struct
{

}

public class Disallow<T>
{
}

public class Example<T> where T : allows ref struct
{
private Allow<T> fieldOne; // Allowed. T is allowed to be a ref struct
private Disallow<T> 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

- <xref:System.Collections.Generic>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<LangVersion>preview</LangVersion>
</PropertyGroup>
</Project>

0 comments on commit d3acc5b

Please sign in to comment.