Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 117 additions & 0 deletions release-notes/11.0/preview/preview5/csharp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# C# in .NET 11 Preview 5 - Release Notes

.NET 11 Preview 5 includes the following C# language and compiler updates:

- [Closed class hierarchies](#closed-class-hierarchies)
- [Union declarations and union patterns](#union-declarations-and-union-patterns)
- [Unsafe code focuses on unsafe operations](#unsafe-code-focuses-on-unsafe-operations)

C# updates:

- [What's new in C# 15](https://learn.microsoft.com/dotnet/csharp/whats-new/csharp-15)

## Closed class hierarchies

A `closed` class can only be directly derived from in the same assembly. The compiler can then use the known derived types when it checks switch-expression exhaustiveness ([dotnet/roslyn #83120](https://github.com/dotnet/roslyn/pull/83120), [dotnet/roslyn #83736](https://github.com/dotnet/roslyn/pull/83736)).

```csharp
public closed record class GateState;
public record class Closed : GateState;
public record class Open(float Percent) : GateState;

static string Describe(GateState state) => state switch
{
Closed => "closed",
Open(var percent) => $"{percent}% open"
};
```

Closed class rules:

- A `closed` class is implicitly abstract.
- Direct subtypes must be declared in the same assembly as the closed base class.
- A switch expression over a closed class is exhaustive when it handles all reachable direct subtypes.

In this preview, projects that define closed classes also need the compiler support attribute available to the compilation:

```csharp
namespace System.Runtime.CompilerServices;

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public sealed class ClosedAttribute : Attribute { }
```

## Union declarations and union patterns

A `union` declaration creates a value type whose value can be one of a fixed set of case types. Pattern matching unwraps the union value, and switch expressions are exhaustive when they handle every case type ([dotnet/roslyn #83705](https://github.com/dotnet/roslyn/pull/83705)).

```csharp
public record class Dog(string Name);
public record class Cat(int Lives);

public union Pet(Dog, Cat);

static string Describe(Pet pet) => pet switch
{
Dog(var name) => $"dog: {name}",
Cat(var lives) => $"cat: {lives}"
};
```

Union rules in this preview:

- The compiler generates a public constructor for each case type.
- A case value converts implicitly to the union type.
- Pattern matching applies most patterns to the union's contained value.
- Ambiguous union conversions are errors ([dotnet/roslyn #83625](https://github.com/dotnet/roslyn/pull/83625)).

The compiler also recognizes hand-authored union types marked with `System.Runtime.CompilerServices.UnionAttribute`, including union members supplied by a nested `IUnionMembers` provider interface ([dotnet/roslyn #83048](https://github.com/dotnet/roslyn/pull/83048), [dotnet/roslyn #83208](https://github.com/dotnet/roslyn/pull/83208)).

In this preview, projects that declare unions need the compiler support types available to the compilation:

```csharp
namespace System.Runtime.CompilerServices;

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false)]
public sealed class UnionAttribute : Attribute { }

public interface IUnion
{
object? Value { get; }
}
```

## Unsafe code focuses on unsafe operations

Unsafe Evolution continues in Preview 5. Pointer types and pointer values can now appear outside an `unsafe` context; the `unsafe` boundary moves to operations that dereference unmanaged memory or call members that are marked `unsafe` ([dotnet/roslyn #83133](https://github.com/dotnet/roslyn/pull/83133), [dotnet/roslyn #83295](https://github.com/dotnet/roslyn/pull/83295), [dotnet/roslyn #83452](https://github.com/dotnet/roslyn/pull/83452)).

```csharp
int value = 42;
int* pointer = &value;

unsafe
{
System.Console.WriteLine(*pointer);
}
```

The pointer declaration and address-of expression are allowed outside the `unsafe` block. The pointer dereference still requires an `unsafe` context.

The stack allocation rule is now opt-in: a `stackalloc` converted to `Span<T>` or `ReadOnlySpan<T>` inside a `[SkipLocalsInit]` member only requires an `unsafe` context when the project opts in to the updated memory-safety rules ([dotnet/roslyn #83639](https://github.com/dotnet/roslyn/pull/83639)).

<!-- Filtered features (significant engineering work, but too niche for release notes):
- Non-virtual static interface members without default interface method runtime support: implementation detail needed by union member providers, not a standalone feature for most readers.
- Compiler feature-status tracking PRs for compound assignment in initializers and typeless receivers: documentation/status updates only; no shipped compiler behavior to describe.
- Additional Razor, LSP, and IDE changes: outside the C# language/compiler product boundary for these notes.
- Caching compiler follow-up PRs: Preview 4 already documented the opt-in VBCSCompiler cache; Preview 5 changes were operational follow-up.
-->

## Community contributors

Thank you contributors! ❤️

- [@Crashdummyy](https://github.com/dotnet/roslyn/pulls?q=is%3Apr+is%3Amerged+author%3ACrashdummyy)
- [@CyrusNajmabadi](https://github.com/dotnet/roslyn/pulls?q=is%3Apr+is%3Amerged+author%3ACyrusNajmabadi)
- [@DoctorKrolic](https://github.com/dotnet/roslyn/pulls?q=is%3Apr+is%3Amerged+author%3ADoctorKrolic)
- [@GustavEikaas](https://github.com/dotnet/roslyn/pulls?q=is%3Apr+is%3Amerged+author%3AGustavEikaas)
- [@olegtk](https://github.com/dotnet/roslyn/pulls?q=is%3Apr+is%3Amerged+author%3Aolegtk)
Loading