Skip to content

Commit c070249

Browse files
committed
Final reviews.
1 parent f4b587f commit c070249

9 files changed

Lines changed: 35 additions & 27 deletions

File tree

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
title: "Records"
33
description: Learn about the record modifier for class and struct types in C#. Records provide standard support for value based equality on instances of record types.
4-
ms.date: 01/14/2026
4+
ms.date: 06/05/2026
55
f1_keywords:
66
- "record_CSharpKeyword"
77
helpviewer_keywords:
@@ -228,14 +228,14 @@ The result of a `with` expression has the same run-time type as the expression's
228228

229229
### `PrintMembers` formatting in derived records
230230

231-
The synthesized `PrintMembers` method of a derived record type calls the base implementation. The result is that all public properties and fields of both derived and base types are included in the `ToString` output, as shown in the following example:
231+
The synthesized `PrintMembers` method of a derived record type calls the base implementation. The result is that the `ToString` output includes all public properties and fields of both derived and base types, as shown in the following example:
232232

233233
:::code language="csharp" source="snippets/shared/RecordType.cs" id="ToStringInheritance":::
234234

235-
You can provide your own implementation of the `PrintMembers` method. If you do that, use the following signature:
235+
You can provide your own implementation of the `PrintMembers` method. If you do, use the following signature:
236236

237237
* For a `sealed` record that derives from `object` (doesn't declare a base record): `private bool PrintMembers(StringBuilder builder)`;
238-
* For a `sealed` record that derives from another record (note that the enclosing type is `sealed`, so the method is effectively `sealed`): `protected override bool PrintMembers(StringBuilder builder)`;
238+
* For a `sealed` record that derives from another record (the enclosing type is `sealed`, so the method is effectively `sealed`): `protected override bool PrintMembers(StringBuilder builder)`;
239239
* For a record that isn't `sealed` and derives from object: `protected virtual bool PrintMembers(StringBuilder builder);`
240240
* For a record that isn't `sealed` and derives from another record: `protected override bool PrintMembers(StringBuilder builder);`
241241

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
description: "abstract - C# Reference"
33
title: "abstract keyword"
4-
ms.date: 01/21/2026
4+
ms.date: 06/06/2026
55
f1_keywords:
66
- "abstract"
77
- "abstract_CSharpKeyword"

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

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,7 @@ ai-usage: ai-assisted
1313
---
1414
# closed (C# Reference)
1515

16-
Starting in C# 15, you can apply the `closed` modifier to a class to declare a *closed hierarchy*. A closed class can only be derived from within its declaring assembly. Because the set of direct descendants is fixed, a `switch` expression that handles each direct descendant exhausts the closed base type and doesn't need a default arm.
17-
18-
[!INCLUDE[csharp-version-note](../includes/initial-version.md)]
16+
Starting in C# 15, you can apply the `closed` modifier to a class to declare a *closed hierarchy*. You can only derive a direct subtype from a closed class within its declaring assembly. Because the set of direct descendants is fixed, a `switch` expression that handles each direct descendant exhausts the closed base type and doesn't need a default arm.
1917

2018
```csharp
2119
// Assembly 1
@@ -38,17 +36,28 @@ public record class RetryableFailed(string Error, int Attempts) : Failed(Error);
3836

3937
If you want to prevent derivation from `Failed` as well, declare it as `sealed` or `closed`.
4038

39+
[!INCLUDE[csharp-version-note](../includes/initial-version.md)]
40+
41+
> [!NOTE]
42+
>`closed` is a contextual keyword. It has special meaning only when it appears as a modifier on a class declaration. You can continue to use `closed` as an identifier in other contexts. If you need to use `closed` as an identifier in a position where the modifier would also be valid, prefix it with `@` (for example, `@closed`) to tell the compiler to treat it as an identifier rather than the modifier.
43+
4144
## Declaration rules
4245

4346
The `closed` modifier is a class modifier:
4447

4548
- A `closed` class is implicitly [`abstract`](abstract.md). You can't combine `closed` with `sealed`, `static`, or an explicit `abstract` modifier.
46-
- A direct subtype of a closed class must be declared in the same assembly and module as the closed base class.
49+
- You must declare a direct subtype of a closed class in the same assembly and module as the closed base class.
4750
- A class that derives from a closed class isn't itself closed. Apply the `closed` modifier again if you want a derived class to also be closed.
4851

4952
If a generic class directly derives from a `closed` class, every type parameter on the derived class must be used in the base class specification. This rule isn't about the `closed` modifier itself: a *closed constructed type* is a generic type whose type arguments are fully specified (such as `Tree<int>`), as opposed to an *open type* like `Tree<T>`. The rule ensures that each closed constructed type of the base class has exactly one corresponding closed constructed type among its direct descendants, so the compiler can reason about exhaustiveness.
5053

51-
:::code language="csharp" source="./snippets/shared/Closed.cs" id="GenericRule":::
54+
```csharp
55+
public closed record class Tree<T>;
56+
57+
public record class Leaf<T>(T Value) : Tree<T>; // OK: 'T' appears in the base class
58+
public record class Branch<T>(Tree<T> Left, Tree<T> Right) : Tree<T>; // OK: 'T' appears in the base class
59+
public record class Constant<U>(U Value) : Tree<int> { } // Error: 'U' isn't used in the base class
60+
```
5261

5362
## Exhaustive switch expressions
5463

@@ -72,10 +81,6 @@ A type parameter constrained to a closed class is treated as that closed class f
7281

7382
This rule applies whether the type parameter appears on a method or on the containing type.
7483

75-
## Contextual keyword
76-
77-
`closed` is a contextual keyword. It has special meaning only when it appears as a modifier on a class declaration. You can continue to use `closed` as an identifier in other contexts. If you need to use `closed` as an identifier in a position where the modifier would also be valid, prefix it with `@` (for example, `@closed`) to tell the compiler to treat it as an identifier rather than the modifier.
78-
7984
## C# language specification
8085

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

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
description: "C# Keywords: Find the reference material for the predefined keywords and contextual keywords defined in the C# language."
33
title: "C# Keywords and contextual keywords"
4-
ms.date: 01/21/2026
4+
ms.date: 06/05/2026
55
f1_keywords:
66
- "cs.keywords"
77
helpviewer_keywords:

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
description: "sealed modifier - C# Reference"
33
title: "sealed modifier"
4-
ms.date: 01/22/2026
4+
ms.date: 06/05/2026
55
f1_keywords:
66
- "sealed"
77
- "sealed_CSharpKeyword"

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

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,6 @@ public record class Completed(TimeSpan Elapsed) : JobStatus;
88
public record class Failed(string Error) : JobStatus;
99

1010
//<GenericRule>
11-
public closed record class Tree<T>;
12-
13-
public record class Leaf<T>(T Value) : Tree<T>; // OK: 'T' appears in the base class
14-
public record class Branch<T>(Tree<T> Left, Tree<T> Right) : Tree<T>; // OK: 'T' appears in the base class
15-
// public record class Constant<U>(U Value) : Tree<int> { } // Error: 'U' isn't used in the base class
1611
//</GenericRule>
1712

1813
public static class ClosedSwitchExamples

docs/csharp/language-reference/operators/patterns.md

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
title: "Patterns - Pattern matching using the is and switch expressions."
33
description: "Learn about the patterns supported by the `is` and `switch` expressions. Combine multiple patterns using the `and`, `or`, and `not` operators."
4-
ms.date: 03/20/2026
4+
ms.date: 06/05/2026
55
f1_keywords:
66
- "and_CSharpKeyword"
77
- "or_CSharpKeyword"
@@ -318,7 +318,7 @@ For example, a closed `public` base class can have an `internal` direct descenda
318318

319319
:::code language="csharp" source="snippets/patterns/ClosedHierarchyPatterns.cs" id="ShapeNameCrossAssembly":::
320320

321-
To restore exhaustiveness in assembly 2, add a discard arm (`_ => ...`) or make every direct descendant at least as accessible as the closed base type.
321+
To restore exhaustiveness in assembly 2, add a discard arm (`_ => ...`), add a base class arm (`Shape` in the preceding example), or make every direct descendant at least as accessible as the closed base type.
322322

323323
When the governing type is nullable, `null` is an additional value the switch must handle. A switch over `PaymentMethod?` that omits a `null` arm isn't exhaustive even when every direct descendant is matched.
324324

@@ -347,7 +347,15 @@ string Category(Vehicle v) => v switch
347347
};
348348
```
349349

350-
If you want exhaustiveness to follow the hierarchy further down, declare `Car` itself `closed`. The compiler then treats `Sedan` (and any other direct descendant of `Car`) as part of an exhaustive set rooted at `Car`, so a switch arm of `Car` no longer satisfies exhaustiveness on its own when other direct descendants of `Car` exist. Marking `Car` `closed` also makes it implicitly `abstract`, which means you can no longer create instances of `Car` directly. That might not fit your design. If you need `Car` to remain instantiable, leave it open and dispatch on the specific subtypes you care about by ordering arms as shown earlier.
350+
If you want exhaustiveness to follow the hierarchy further down, declare `Car` itself `closed`. The compiler then treats every direct descendant of `Car` (such as `Sedan`) as the exhaustive set rooted at `Car`. A switch whose governing type is `Car` is exhaustive when it does any of the following:
351+
352+
- Handle every direct descendant of `Car` with its own arm.
353+
- Include a `Car` arm, which covers every `Car` value (including all subtypes).
354+
- Include a discard arm (`_ => ...`) or an arm for a base type of `Car`, such as `Vehicle`.
355+
356+
A switch whose governing type is `Vehicle` has choices: Code that handles `Car` and `Truck` is still exhaustive, because the `Car` arm covers every subtype of `Car`. Marking `Car` `closed` simply gives you a second option for that switch. You can keep the single `Car` arm, or replace it with one arm per direct descendant of `Car` (alongside the `Truck` arm) and still be exhaustive.
357+
358+
Marking `Car` `closed` also makes it implicitly `abstract`, which means you can no longer create instances of `Car` directly. That might not fit your design. If you need `Car` to remain instantiable, leave it open and dispatch on the specific subtypes you care about by ordering arms as shown earlier.
351359

352360
### Type parameter governing types
353361

docs/csharp/language-reference/operators/snippets/patterns/ClosedHierarchyPatterns.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
namespace Patterns.ClosedHierarchy;
1+
namespace Patterns.ClosedHierarchy;
22

33
// <PaymentMethodTypes>
44
public closed record class PaymentMethod;
@@ -60,7 +60,7 @@ public record class Car(int Doors) : Vehicle;
6060
public record class Truck(double PayloadTons) : Vehicle;
6161

6262
// Assembly 2
63-
public record class Sedan(int Doors) : Car;
63+
public record class Sedan(int Doors) : Car(Doors);
6464
// </VehicleTypes>
6565

6666
public static class VehicleExamples

docs/csharp/whats-new/csharp-15.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
title: What's new in C# 15
33
description: Get an overview of the new features in C# 15. C# 15 ships with .NET 11.
4-
ms.date: 03/20/2026
4+
ms.date: 06/05/2026
55
ms.topic: whats-new
66
ms.update-cycle: 365-days
77
ai-usage: ai-assisted

0 commit comments

Comments
 (0)