dotnet / dotnet/docs

Union types reference: `pet is Pet` note is wrong, and the null-handling sample implies a warning that never appears

Open
#55,951 2 comments 0 reactions 1 assignee Claimed by @BillWagner View on GitHub
:checkered_flag: Release: .NET 11 :pushpin: seQUESTered
Dominant language
No language data
Stars
4.8k
Forks
6.1k
Avg merge
19h 10m
Merged PRs (30d)
268

Description

**Page:** https://learn.microsoft.com/dotnet/csharp/language-reference/builtin-types/union

### 1. "a pattern like `pet is Pet` typically doesn't match"

The note under *Union pattern matching* says this pattern typically doesn't match, because `Pet` is tested against the union's contents. On .NET 11 RC 1 the result is the opposite: it always matches, including when `Value` is `null`. The compiler also flags it as a constant result:

```csharp
Pet real = new Dog("Rex");
Pet none = default;
Console.WriteLine(real is Pet); // True
Console.WriteLine(none is Pet); // True
// warning CS0183: The given expression is always of the provided ('Pet') type
```

This matches the feature specification, which says the union instance is tested first and then its value. The note should say that `pet is Pet` is always true, and so is useless as a has-a-value test. `pet.Value is not null` or `pet is not null` are the correct checks.

### 2. The *Union exhaustiveness* null-handling sample

The text says that when `Value`'s null state is "maybe null", you must also handle `null` to avoid a warning. It then says this can happen when the union expression is the default value, "as shown in the preceding sample" (`Pet pet = default;`).

But in that sample (and whenever no case type is nullable), the compiler gives **no warning** if the `null` arm is removed. This is consistent with the *Nullability* rules further down the same page: `Value` defaults to "not null" when no case type is nullable. The compiler team confirmed that rule as the current design in dotnet/roslyn#85054. Without the arm, the switch compiles clean and then throws at runtime:

```csharp
Pet pet = default;
var name = pet switch { Dog d => d.Name, Cat c => c.Name, Bird b => b.Name }; // no warning
// System.Runtime.CompilerServices.SwitchExpressionException at runtime
```

The same holds for an array slot (`new Pet[1][0]`), an unassigned field, and a parameter. `CS8655` appears only when a case type is nullable, or after the code has already tested `is null`.

Suggested wording: say that the `null` arm is needed for **correctness** whenever a union can be `default` (fields, array elements, `default(T)`), and that the compiler **will not** ask for it unless a case type is nullable. The current text implies a safety net that doesn't exist.

**Environment:** .NET SDK `11.0.100-rc.1.26425.128`, `enable`, Windows 11 x64.

---
[Associated WorkItem - 632609](https://dev.azure.com/msft-skilling/Content/_workitems/edit/632609)

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.