Skip to content

Commit 82a7895

Browse files
BillWagnerCopilotCopilot
authored
Check all C# 15 docs against updated feature specs and .NET 11 preview 7 behavior (dotnet#55444)
* First pass: Fix dotnet#55322 Elaborate on the explanation for IUnionProviders. * Document C# 15 unsafe updates (dotnet#55330) Add unsafe(expression) coverage to What's new in C# 15 and to the Language Reference (unsafe-code.md and keywords/unsafe.md), including a field-initializer example and an await-with-unsafe-expression example verified against the .NET 11 Preview 6 SDK. Explain why the expression form is useful where an unsafe block can't appear syntactically (field initializers, constructor initializers, catch filters), without claiming await is permitted inside an unsafe context; it remains illegal (CS4004). Update keywords/safe.md: the compiler now recognizes the safe modifier on extern members and explicit-layout fields (verified against Preview 6), but caller-safety/requires-unsafe enforcement has no public opt-in and isn't active under ordinary preview settings. Align xmldoc/recommended-tags.md safety guidance with the same caveat so it doesn't imply caller obligations are already enforced. Fixes dotnet#55330 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4c0acfc3-7e62-4739-a09e-2dedc26b27af * Clarify C# 15 union pattern matching (dotnet#55330) Fix residual wording in operators/patterns.md and the "Non-boxing access pattern" section of builtin-types/union.md that conflated TryGetValue and HasValue as a single combined mechanism. State plainly that TryGetValue supports type-pattern checks without boxing and HasValue supports null-pattern checks; neither is a fallback for the other, and the compiler checks Value for the corresponding pattern when a member is omitted. Cross-link to the detailed "Union member providers" section (accepted in dotnet#55322) instead of duplicating its explanation. Fixes dotnet#55330 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4c0acfc3-7e62-4739-a09e-2dedc26b27af * Update C# 15 closed hierarchy docs (dotnet#55330) Remove the obsolete ClosedAttribute workaround from What's new and the shared snippet projects. The .NET 11 Preview 6 runtime already supplies System.Runtime.CompilerServices.IsClosedTypeAttribute (verified: the compiler synthesizes it automatically), so projects no longer need to declare a substitute attribute. Fix a discovered inaccuracy while validating snippets against the Preview 6 SDK: exhaustiveness checking for a switch governed by a type parameter constrained to a closed class isn't implemented yet, even though the specification defines it and the docs claimed it worked. Keep the example (it shows valid syntax from the specification) but add an accurate NOTE explaining the current compiler limitation in both closed.md and patterns.md, instead of leaving readers with example code whose "no warning" comment contradicts what the compiler actually reports. Fixes dotnet#55330 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4c0acfc3-7e62-4739-a09e-2dedc26b27af * Clarify C# 15 collection expression arguments (dotnet#55330) Add a restriction to operators/collection-expressions.md stating that with(...) arguments don't affect conversion, overload resolution, or type inference: only the presence of a with(...) element affects whether a conversion from the collection expression to a candidate target type exists, and the arguments themselves are ignored. This matches the resolved LDM decision in the collection expression arguments feature specification, verified against the .NET 11 Preview 6 SDK (overload resolution stays ambiguous and type arguments still can't be inferred when with(...) supplies them). Fixes dotnet#55330 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4c0acfc3-7e62-4739-a09e-2dedc26b27af * proofread Proofread and grammar check all changed files in this PR. * Validate against preview 7 * Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Copilot-Session: 4c0acfc3-7e62-4739-a09e-2dedc26b27af
1 parent bf2669b commit 82a7895

16 files changed

Lines changed: 133 additions & 72 deletions

File tree

docs/csharp/language-reference/builtin-types/snippets/unions/MemberProvider.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ public interface IUnionMembers
1212
static Outcome<T> Create(Exception? value) => new(value);
1313
object? Value { get; }
1414

15-
// only when needed
15+
// Optional but recommended: TryGetValue enables efficient pattern matching
1616
bool TryGetValue(out T value);
1717
bool TryGetValue(out Exception value);
1818
}

docs/csharp/language-reference/builtin-types/union.md

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
title: "Union types"
33
description: Learn about union types in C#. Unions express values from a closed set of types with exhaustive pattern matching support.
4-
ms.date: 06/05/2026
4+
ms.date: 08/14/2026
55
f1_keywords:
66
- "union_CSharpKeyword"
77
helpviewer_keywords:
@@ -141,7 +141,7 @@ Any class or struct with a `[Union]` attribute is a *union type* if it follows t
141141
- One or more public constructors, each with a single by-value or `in` parameter. The parameter type of each constructor defines a *case type*.
142142
- A public `Value` property of type `object?` (or `object`) with a `get` accessor.
143143

144-
All the preceding union members must be public. The compiler uses these members to implement union conversions, pattern matching, and exhaustiveness checks. You can also implement the [non-boxing access pattern](#non-boxing-access-pattern) or create a [class-based union type](#class-based-union-types). Your custom union type can add additional members.
144+
All the preceding union members must be public. The compiler uses these members to implement union conversions, pattern matching, and exhaustiveness checks. You can also implement the [non-boxing access pattern](#non-boxing-access-pattern) or create a [class-based union type](#class-based-union-types). Your custom union type can add more members.
145145

146146
The compiler assumes that custom union types satisfy these behavioral rules:
147147

@@ -167,11 +167,28 @@ A custom union type can optionally implement the *non-boxing access pattern* to
167167

168168
:::code language="csharp" source="snippets/unions/NonBoxingAccess.cs" id="NonBoxingExample":::
169169

170-
The compiler prefers `TryGetValue` over the `Value` property when implementing pattern matching, which avoids boxing value types.
170+
For pattern matching, the compiler calls `TryGetValue` for type-pattern checks (such as `union is T`) and `HasValue` for null-pattern checks (such as `union is null`), avoiding boxing for value-type cases. Each member applies to its own pattern kind—neither is a fallback for the other. When a member is missing, the compiler checks the `Value` property for that pattern instead. For more information, see [How the compiler generates code for pattern matching](#union-member-providers).
171171

172172
### Union member providers
173173

174-
A union type can delegate its union members to a nested `IUnionMembers` interface. When this interface is present, the `union` type behaves as a *union member provider*. The compiler generates code to call the `IUnionMembers` interface. It won't generate calls to members declared on the union type that aren't members of the nested `IUnionMembers` interface. As the following example shows, that means you must add the necessary factory methods and the appropriate `TryGetValue` methods for all case types:
174+
A union type can delegate its union members to a nested `IUnionMembers` interface. When this interface is present, the `union` type behaves as a *union member provider*, and the compiler generates calls only to the members declared on `IUnionMembers`. Members declared on the union type itself, but omitted from the `IUnionMembers` interface, aren't used by the compiler.
175+
176+
When using a union member provider, the interface must declare these required members:
177+
178+
- **Static `Create` methods**: One for each case type, with a single parameter and a return type identity-convertible to the union type. These methods establish your case types.
179+
- **`Value` property**: A public `object` or `object?` property with a `get` accessor that returns the contained value.
180+
181+
You can optionally declare these members on the interface so the compiler generates more efficient pattern matching code:
182+
183+
- **`TryGetValue` methods**: One for each case type, returning `bool` with an `out` parameter of the case type.
184+
- **`HasValue` property**: A public `bool` property with a `get` accessor returning `true` when `Value` isn't `null`.
185+
186+
**How the compiler generates code for pattern matching**: `TryGetValue` and `HasValue` apply to different kinds of patterns—neither is a fallback for the other:
187+
188+
- For a *type pattern*, such as `union is T`, the compiler calls `TryGetValue(out T value)` when declared, extracting the value and applying the pattern to it without boxing. Otherwise, the compiler applies the pattern to the `Value` property, which performs a runtime type check against `T` and can box the value when `T` is a value type.
189+
- For a *null pattern*, such as `union is null`, the compiler calls `HasValue` when declared to test whether the union contains a value. Otherwise, the compiler applies the null pattern to the `Value` property directly.
190+
191+
Each member is independently optional: without `TryGetValue`, type patterns use `Value`; without `HasValue`, null patterns use `Value`. Declaring either member lets the compiler generate more efficient, strongly typed code for that pattern kind:
175192

176193
:::code language="csharp" source="snippets/unions/MemberProvider.cs" id="MemberProvider":::
177194

docs/csharp/language-reference/keywords/closed.md

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
title: "closed modifier"
33
description: "Learn about the closed class modifier in C#. A closed class restricts derivation to its declaring assembly so consumers can write exhaustive switch expressions over its direct descendants."
4-
ms.date: 06/02/2026
4+
ms.date: 08/14/2026
55
f1_keywords:
66
- "closed"
77
- "closed_CSharpKeyword"
@@ -75,12 +75,10 @@ For more information about how the compiler determines exhaustiveness, including
7575

7676
## Type parameters constrained to a closed type
7777

78-
A type parameter constrained to a closed class is treated as that closed class for exhaustiveness checks. A `switch` expression whose governing value has such a type parameter is exhaustive when it handles every direct descendant of the closed constraint:
78+
The [closed hierarchies feature specification](~/_csharplang/proposals/csharp-15.0/closed-hierarchies.md#exhaustiveness-of-type-parameters-constrained-to-closed-type) treats a type parameter constrained to a closed class the same as that closed class for exhaustiveness checks: a `switch` expression whose governing value has such a type parameter is exhaustive when it handles every direct descendant of the closed constraint, regardless of whether the type parameter appears on a method or on the containing type:
7979

8080
:::code language="csharp" source="./snippets/shared/Closed.cs" id="TypeParameterConstrained":::
8181

82-
This rule applies whether the type parameter appears on a method or on the containing type.
83-
8482
## C# language specification
8583

8684
For more information, see the [Closed hierarchies](~/_csharplang/proposals/csharp-15.0/closed-hierarchies.md) feature specification.

docs/csharp/language-reference/keywords/safe.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
description: "safe modifier - C# Reference"
33
title: "safe modifier"
4-
ms.date: 06/17/2026
4+
ms.date: 08/14/2026
55
ai-usage: ai-assisted
66
f1_keywords:
77
- "safe_CSharpKeyword"
@@ -14,29 +14,29 @@ helpviewer_keywords:
1414
The `safe` contextual keyword attests that a declaration is sound in places where the [updated memory safety model](../unsafe-code.md#the-updated-memory-safety-model-preview) requires you to make the safety choice explicit. You apply `safe` as a modifier on a declaration that the compiler can't classify on its own, such as an `extern` member or a field in a struct with explicit layout. The `safe` modifier is the counterpart to [`unsafe`](unsafe.md): `safe` attests that callers need no `unsafe` context, while `unsafe` propagates the obligation to audit safety to the caller.
1515

1616
> [!IMPORTANT]
17-
> The `safe` keyword is part of the updated memory safety model, a preview feature in C# 15 and .NET 11. The compiler in .NET 11 Preview 5 doesn't yet recognize the keyword. To follow the feature, set the [`LangVersion`](../compiler-options/language.md#langversion) compiler option to `preview`. For the full design, see the [memory safety feature specification](~/_csharplang/proposals/unsafe-evolution.md). The code in this article shows the proposed syntax and doesn't compile with the current preview compiler.
17+
> The `safe` keyword is part of the updated memory safety model, a preview feature in C# 15 and .NET 11. The compiler accepts `safe` as a modifier on `extern` members and explicit-layout fields. However, there's no public opt-in for the updated caller-safety rules yet, so the compiler doesn't enforce the safety choice that `safe` and [`unsafe`](unsafe.md) express: omitting both modifiers doesn't produce an error, and neither modifier changes what callers can do. To follow the feature, set the [`LangVersion`](../compiler-options/language.md#langversion) compiler option to `preview`. For the full design, see the [memory safety feature specification](~/_csharplang/proposals/unsafe-evolution.md).
1818
1919
## Extern members
2020

2121
An `extern` member calls into native code, so the compiler can't classify its safety. Under the updated model, you mark every `extern` declaration, including a `LibraryImport` partial method, either `safe` or `unsafe`:
2222

2323
```csharp
24-
// Preview: illustrates the updated model, which the current compiler doesn't enforce yet.
24+
// Compiles under LangVersion preview, but the safety choice isn't enforced yet.
2525
[LibraryImport("libc")]
2626
internal static safe partial int getpid();
2727

2828
[LibraryImport("libc", StringMarshalling = StringMarshalling.Utf8)]
2929
internal static unsafe partial nint strlen(byte* str);
3030
```
3131

32-
`getpid` takes no parameters and returns a primitive, so the author attests that the call is safe, and callers use it without an `unsafe` context. `strlen` takes a raw pointer that the native code dereferences, so the declaration is `unsafe` and propagates the obligation to its callers. Omitting both modifiers is an error, which forces you to make the safety decision.
32+
`getpid` takes no parameters and returns a primitive, so the author attests that the call is safe, and callers use it without an `unsafe` context. `strlen` takes a raw pointer that the native code dereferences, so the declaration is `unsafe` and propagates the obligation to its callers. Omitting both modifiers is intended to be an error under the updated model, but the compiler doesn't yet enforce that rule because there's no public opt-in for the updated rules.
3333

3434
## Explicit-layout fields
3535

3636
In a struct with `[StructLayout(LayoutKind.Explicit)]`, fields can overlap in memory, so the compiler can't reason about whether a read through one field is sound. You mark every field of such a struct either `safe` or `unsafe`:
3737

3838
```csharp
39-
// Preview
39+
// Compiles under LangVersion preview, but the safety choice isn't enforced yet.
4040
[StructLayout(LayoutKind.Explicit)]
4141
internal struct Union
4242
{
@@ -48,7 +48,7 @@ internal struct Union
4848
}
4949
```
5050

51-
A field that holds a native pointer, or whose type otherwise carries an invariant the type system can't express, is `unsafe`. A field whose type is fully described by the type system is `safe`. As with `extern` members, omitting both modifiers is an error.
51+
A field that holds a native pointer, or whose type otherwise carries an invariant the type system can't express, is `unsafe`. A field whose type is fully described by the type system is `safe`. As with `extern` members, omitting both modifiers is intended to be an error under the updated model, but the compiler doesn't yet enforce that rule.
5252

5353
## C# language specification
5454

docs/csharp/language-reference/keywords/snippets/shared/Closed.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ public static class ClosedSwitchExamples
4242
Running(var percent) => $"{percent}% complete",
4343
Completed(var elapsed) => $"finished in {elapsed.TotalSeconds:F1}s",
4444
Failed(var error) => $"failed: {error}",
45-
// No warning: 'X' is constrained to a closed type, so its direct descendants exhaust the switch.
45+
// No warning: 'X' is constrained to a closed type, so the compiler treats
46+
// this switch as exhaustive because every direct descendant is handled.
4647
};
4748
//</TypeParameterConstrained>
4849
}

docs/csharp/language-reference/keywords/snippets/shared/ClosedAttribute.cs

Lines changed: 0 additions & 6 deletions
This file was deleted.

docs/csharp/language-reference/keywords/unsafe.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
description: "unsafe keyword - C# Reference"
33
title: "unsafe keyword"
4-
ms.date: 06/16/2026
4+
ms.date: 08/14/2026
55
f1_keywords:
66
- "unsafe_CSharpKeyword"
77
- "unsafe"
@@ -43,7 +43,7 @@ To compile unsafe code, you must specify the [**AllowUnsafeBlocks**](../compiler
4343
> [!NOTE]
4444
> The [memory safety](../unsafe-code.md#the-updated-memory-safety-model-preview) preview feature available in C# 15 narrows the operations that require an `unsafe` context.
4545
> An `unsafe` context is no longer required for creating a pointer, the `fixed` statement, converting a `stackalloc` expression to a pointer, and using `sizeof` on an unmanaged type.
46-
> Only operations that access the pointed-to memory, such as pointer indirection, still require an `unsafe` context. A later preview also changes `unsafe` on a member to mark it as *requires-unsafe*, so callers must use the member from an `unsafe` context.
46+
> Only operations that access the pointed-to memory, such as pointer indirection, still require an `unsafe` context. The same preview also adds an `unsafe(expression)` form that establishes an unsafe context for a single expression, for positions where an `unsafe` block can't appear, such as a field initializer or a `catch` filter. For more information, see [Unsafe expressions](../unsafe-code.md#unsafe-expressions). The preview also gives the `unsafe` modifier on a member a new meaning: the compiler recognizes it as marking the member *requires-unsafe*. Caller enforcement of that obligation isn't implemented yet, so marking a member `unsafe` currently has no effect on its callers. For more information, see [Caller-unsafe members](../unsafe-code.md#caller-unsafe-members).
4747
4848
## Example
4949

docs/csharp/language-reference/operators/collection-expressions.md

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
title: "Collection expressions (Collection literals)"
33
description: Collection expressions convert to many collection types. You can write literal values, expressions, or other collections to create a new collection.
4-
ms.date: 02/04/2026
4+
ms.date: 08/14/2026
55
helpviewer_keywords:
66
- "Collection expressions"
77
---
@@ -105,31 +105,31 @@ The first parameter provides the name of the *Builder* class. The second attribu
105105

106106
Starting in C# 15, you can pass arguments to the underlying collection's constructor or factory method by using a `with(...)` element as the first element in a collection expression. This feature enables you to specify capacity, comparers, or other constructor parameters directly within the collection expression syntax. For more information, see the [collection expression arguments feature specification](~/_csharplang/proposals/csharp-15.0/collection-expression-arguments.md).
107107

108-
The `with(...)` element must be the first element in the collection expression. The arguments declared in the `with(...)` element are passed to the appropriate constructor or create method based on the target type. You can use any valid expression for the arguments in the `with` element.
108+
The `with(...)` element must be the first element in the collection expression. The arguments declared in the `with(...)` element go to the appropriate constructor or create method based on the target type. You can use any valid expression for the arguments in the `with` element.
109109

110110
### Constructor arguments
111111

112-
When the target type is a class or struct that implements <xref:System.Collections.IEnumerable?displayProperty=nameWithType>, the arguments in `with(...)` are evaluated and the results are passed to the constructor. The compiler uses overload resolution to select the best matching constructor:
112+
When the target type is a class or struct that implements <xref:System.Collections.IEnumerable?displayProperty=nameWithType>, the arguments in `with(...)` are evaluated and the results go to the constructor. The compiler uses overload resolution to select the best matching constructor:
113113

114114
:::code language="csharp" source="./snippets/shared/CollectionExpressionExamples.cs" id="WithArgumentsExamples":::
115115

116116
In the preceding example:
117117

118-
- The `List<string>` constructor with a `capacity` parameter is called with `values.Length * 2`.
119-
- The `HashSet<string>` constructor with an <xref:System.Collections.Generic.IEqualityComparer`1?displayProperty=nameWithType> parameter is called with `StringComparer.OrdinalIgnoreCase`.
118+
- The `List<string>` constructor with a `capacity` parameter gets called with `values.Length * 2`.
119+
- The `HashSet<string>` constructor with an <xref:System.Collections.Generic.IEqualityComparer`1?displayProperty=nameWithType> parameter gets called with `StringComparer.OrdinalIgnoreCase`.
120120
- For interface target types like <xref:System.Collections.Generic.IList`1?displayProperty=nameWithType>, the compiler creates a `List<T>` with the specified capacity.
121121

122122
### Collection builder arguments
123123

124-
For types with a <xref:System.Runtime.CompilerServices.CollectionBuilderAttribute?displayProperty=nameWithType>, the arguments declared in the `with(...)` element are evaluated and the results are passed to the create method *before* the `ReadOnlySpan<T>` parameter. This feature allows create methods to accept configuration parameters:
124+
For types with a <xref:System.Runtime.CompilerServices.CollectionBuilderAttribute?displayProperty=nameWithType>, the arguments declared in the `with(...)` element get evaluated and the results go to the create method *before* the `ReadOnlySpan<T>` parameter. This feature allows create methods to accept configuration parameters:
125125

126126
:::code language="csharp" source="./snippets/shared/CollectionExpressionExamples.cs" id="BuilderClassWithComparer":::
127127

128128
You can then use the `with(...)` element to pass the comparer:
129129

130130
:::code language="csharp" source="./snippets/shared/CollectionExpressionExamples.cs" id="WithBuilderArgumentsExample":::
131131

132-
The create method is selected using overload resolution based on the arguments provided. The `ReadOnlySpan<T>` containing the collection elements is always the last parameter.
132+
The create method gets selected by using overload resolution based on the arguments you provide. The `ReadOnlySpan<T>` containing the collection elements is always the last parameter.
133133

134134
### Interface target types
135135

@@ -149,3 +149,4 @@ The `with(...)` element has the following restrictions:
149149
- It must be the first element in the collection expression.
150150
- Arguments can't have `dynamic` type.
151151
- It's not supported for arrays or span types (`Span<T>`, `ReadOnlySpan<T>`).
152+
- The arguments in `with(...)` don't affect whether a conversion exists from the collection expression to a candidate target type. The compiler ignores them during overload resolution and type inference. Only the presence of a `with(...)` element, not its arguments, affects whether the conversion exists.

0 commit comments

Comments
 (0)