diff --git a/release-notes/11.0/preview/preview7/csharp.md b/release-notes/11.0/preview/preview7/csharp.md new file mode 100644 index 0000000000..69847b626d --- /dev/null +++ b/release-notes/11.0/preview/preview7/csharp.md @@ -0,0 +1,122 @@ +# C# in .NET 11 Preview 7 - Release Notes + +.NET 11 Preview 7 includes the following C# language and compiler updates: + +- [Labeled `break` and `continue`](#labeled-break-and-continue) +- [Union patterns match the union or its value](#union-patterns-match-the-union-or-its-value) +- [Exhaustiveness for type parameters constrained to a closed type](#exhaustiveness-for-type-parameters-constrained-to-a-closed-type) +- [Unsafe Evolution: compat mode and `nameof`](#unsafe-evolution-compat-mode-and-nameof) +- [Null check in compiler-synthesized inline-array helpers](#null-check-in-compiler-synthesized-inline-array-helpers) +- [Clearer runtime-async unsupported-feature diagnostic](#clearer-runtime-async-unsupported-feature-diagnostic) + +C# updates: + +- [What's new in C# 15](https://learn.microsoft.com/dotnet/csharp/whats-new/csharp-15) + +## Labeled `break` and `continue` + +`break` and `continue` can now name an enclosing loop or `switch`, so you can exit or continue an outer construct directly from an inner one without threading state through a flag or resorting to `goto` ([dotnet/roslyn#84281](https://github.com/dotnet/roslyn/pull/84281)). The corresponding language proposal lives at [dotnet/csharplang labeled-break-continue](https://github.com/dotnet/csharplang/blob/main/proposals/labeled-break-continue.md). + +Prefix the target loop with a label, then reference it from `break` or `continue`: + +```csharp +string? foundValue = null; +outer: for (int x = 0; x < xMax; x++) +{ + for (int y = 0; y < yMax; y++) + { + if (GetValue(x, y) is { } value && value == target) + { + foundValue = value; + break outer; + } + } +} +``` + +The same label can drive `continue`: + +```csharp +row: for (int i = 0; i < rows; i++) +{ + for (int j = 0; j < cols; j++) + { + if (ShouldSkipRestOfRow(i, j)) continue row; + Process(i, j); + } +} +``` + +The label must be attached directly to a `for`, `foreach`, `while`, `do`, or `switch` statement, and the labeled `break`/`continue` must appear inside that statement. + +## Union patterns match the union or its value + +`union` types now use the **Try-Both** matching approach: when a pattern is applied to a union value, the compiler first tests the pattern against the union instance itself, and — if that test fails — against the union's contained `Value`. This aligns matching semantics with the [updated `unions` speclet](https://github.com/dotnet/csharplang/blob/main/proposals/unions.md#union-matching) and applies to type, `var`, declaration, list, and recursive patterns ([dotnet/roslyn#84323](https://github.com/dotnet/roslyn/pull/84323), [dotnet/roslyn#84365](https://github.com/dotnet/roslyn/pull/84365), [dotnet/roslyn#84418](https://github.com/dotnet/roslyn/pull/84418), [dotnet/roslyn#84531](https://github.com/dotnet/roslyn/pull/84531)). + +```csharp +public record class Dog(string Name); +public record class Cat(int Lives); + +public union Pet(Dog, Cat); + +Pet pet = new Cat(9); + +// Match against the union instance itself +if (pet is Pet) // true — the union type is a valid match target + Console.WriteLine("got a pet"); + +// Match against the contained value +if (pet is Cat { Lives: > 0 } cat) + Console.WriteLine($"cat has {cat.Lives} lives"); +``` + +Alongside this shift, type-checking during binding stops propagating union type information as soon as the output value is narrowed to a specific case type, aligning behavior with non-union scenarios ([dotnet/roslyn#84248](https://github.com/dotnet/roslyn/pull/84248)). Custom union declarations use a new `UnionMatchingMode` property to control how patterns are lowered, replacing earlier ad-hoc flags ([dotnet/roslyn#84436](https://github.com/dotnet/roslyn/pull/84436), [dotnet/roslyn#84499](https://github.com/dotnet/roslyn/pull/84499)). Unions remain a preview feature; enable `preview` to use them, and expect the surface to keep evolving. + +## Exhaustiveness for type parameters constrained to a closed type + +Switch-expression exhaustiveness now understands generic parameters constrained to a `closed` type ([dotnet/roslyn#83979](https://github.com/dotnet/roslyn/pull/83979), tracking [dotnet/roslyn#81039](https://github.com/dotnet/roslyn/issues/81039)). When every direct subtype of the closed base is handled, the compiler no longer warns that the switch is non-exhaustive — even when the input is typed as the type parameter rather than the base type itself. + +```csharp +public closed record class Shape; +public record class Circle(double Radius) : Shape; +public record class Square(double Side) : Shape; + +static double Area(T shape) where T : Shape => shape switch +{ + Circle(var r) => Math.PI * r * r, + Square(var s) => s * s +}; +``` + +The compiler decides `T` cannot introduce any additional case, because every derived type of `Shape` is already covered. The closed-hierarchies metadata format also stabilizes in this preview: `IsClosedTypeAttribute` now carries a `DerivedTypes` property, matching the format decided in [dotnet/runtime#129009](https://github.com/dotnet/runtime/issues/129009) ([dotnet/roslyn#84350](https://github.com/dotnet/roslyn/pull/84350)). + +## Unsafe Evolution: compat mode and `nameof` + +> This is a preview feature for .NET 11. + +Unsafe Evolution continues to refine the boundary between "code that mentions pointers" and "code that dereferences unmanaged memory". This preview delivers two follow-up rules from the [Unsafe Evolution speclet](https://github.com/dotnet/csharplang/blob/main/proposals/unsafe-evolution.md): + +- **Compat mode extends to legacy callers.** Members marked as *requires-unsafe* under the updated memory-safety rules now require an `unsafe` context even when they are called from code that hasn't opted into those rules ([dotnet/roslyn#83660](https://github.com/dotnet/roslyn/pull/83660), fixes [dotnet/roslyn#81967](https://github.com/dotnet/roslyn/issues/81967)). Without this, projects that only bumped `LangVersion` — but not their memory-safety rules version — could end up *less* protected than before by silently calling requires-unsafe members from safe contexts. +- **`nameof` no longer reports requires-unsafe errors.** Referencing a requires-unsafe member inside `nameof(...)` no longer reports an unsafe-context error, matching how `nameof` already handled most other member kinds ([dotnet/roslyn#84325](https://github.com/dotnet/roslyn/pull/84325)). + +Unsafe Evolution remains a preview feature; the exact rules and diagnostics can still change. Follow the [test plan](https://github.com/dotnet/roslyn/issues/81207) for status. + +## Null check in compiler-synthesized inline-array helpers + +Element access and `Span` conversion for an `[InlineArray]` value are lowered to calls into synthesized helpers in `` (`InlineArrayElementRef`, `InlineArrayElementRefReadOnly`, `InlineArrayAsSpan`, `InlineArrayAsReadOnlySpan`). Those helpers previously accepted any byref — including a null byref — and returned a `ref` or `Span` at a caller-controlled offset. Reading or writing through the result did not reliably fault; instead it produced arbitrary reads and writes, and this was reachable from safe code (for example, via `Unsafe.NullRef()`). + +The helpers now null-check the incoming buffer, so forming a `ref` or span from a null inline-array reference throws `NullReferenceException` deterministically ([dotnet/roslyn#84488](https://github.com/dotnet/roslyn/pull/84488), [dotnet/roslyn#84523](https://github.com/dotnet/roslyn/pull/84523), closes [dotnet/roslyn#84344](https://github.com/dotnet/roslyn/issues/84344)). In the common case where the JIT can prove the base is non-null, the check is elided. No source change is needed to pick up the fix — rebuild against the Preview 7 compiler. + +## Clearer runtime-async unsupported-feature diagnostic + +The runtime-async diagnostic that reports "this method uses a feature that runtime-async doesn't support yet" no longer implies that support will eventually arrive for `__arglist` ([dotnet/roslyn#84263](https://github.com/dotnet/roslyn/pull/84263)). `__arglist` cannot be lowered by the runtime-async transform, so the updated message states that plainly. The diagnostic is also removed from the build-only list, since the remaining reporting site runs during initial binding. + +## Community contributors + +Thank you contributors! ❤️ + +- [@corentingallet](https://github.com/dotnet/roslyn/pulls?q=is%3Apr+is%3Amerged+author%3Acorentingallet) +- [@DoctorKrolic](https://github.com/dotnet/roslyn/pulls?q=is%3Apr+is%3Amerged+author%3ADoctorKrolic) +- [@neoGeneva](https://github.com/dotnet/roslyn/pulls?q=is%3Apr+is%3Amerged+author%3AneoGeneva) +- [@seblyng](https://github.com/dotnet/roslyn/pulls?q=is%3Apr+is%3Amerged+author%3Aseblyng) +- [@teo-tsirpanis](https://github.com/dotnet/roslyn/pulls?q=is%3Apr+is%3Amerged+author%3Ateo-tsirpanis)