C# 15 unions make a missed switch case a CS8509 warning at compile time
C# 15 union types make the compiler flag every switch that misses a case with warning CS8509, so the throw-on-unreachable arm can go. They ship with .NET 11, now at RC1 with a go-live license but not final.
The Engineer · Build desk
Marker-interface switch: the compiler demands a _ arm and the developer writes a throw. Adding Discontinued compiles, then the author's run threw InvalidOperationException. Union Pet(Cat, Dog) switch: no _ arm, no warning. Adding Discontinued to a union: warning CS8509 at every missed switch.
- constraint Switch over marker interface Compiler demands a _ arm, so the developer writes a throw for a case that 'can't happen', claim 3
- exposure Interface gets Discontinued A teammate's new Discontinued record compiles; the author's run threw an unhandled InvalidOperationException, claim 4
- capability Union Pet(Cat, Dog) switch Dog and Cat arms with no _ arm give no warning; the compiler knows they are the whole list, claim 6
- capability Union gets Discontinued Build reports warning CS8509 at every switch that missed the new case, claim 9
| Who | How | Kind | Claim |
|---|---|---|---|
| Switch over marker interface | Compiler demands a _ arm, so the developer writes a throw for a case that 'can't happen' | constraint | 3 |
| Interface gets Discontinued | A teammate's new Discontinued record compiles; the author's run threw an unhandled InvalidOperationException | exposure | 4 |
| Union Pet(Cat, Dog) switch | Dog and Cat arms with no _ arm give no warning; the compiler knows they are the whole list | capability | 6 |
| Union gets Discontinued | Build reports warning CS8509 at every switch that missed the new case | capability | 9 |
What happened
- A union declaration names a closed set of case types, a value is exactly one of them, and any case type converts to the union implicitly.
- With a marker interface the compiler forced a discard arm, and a newly added Discontinued record compiled cleanly and then threw at runtime.
- Assigning a type outside the union is a compile error; the post shows a string assigned to a Pet failing with CS0029.
- A union of int and Guid works, mixing two types that could never share a base class or interface.
Why it matters
- decision Teams choose whether a missed union case fails the build. CS8509 is a warning, and the post says it becomes a failed build only when TreatWarningsAsErrors is on.
- constraint Adopting now means building on a release candidate, and Microsoft's docs say parts of the union proposal are not implemented yet.
- exposure Existing marker-interface hierarchies keep their throw arms and their runtime failures until someone rewrites them as unions. The new check covers only switches over a union type.
- capability Case types need no shared base and need not be the team's own, so a closed set can now span types from other libraries.
The `_` arm exists because the compiler cannot know that only three types implement the marker interface. [3] So it demands a fallback, and the usual filler is a throw for a case that "can't happen". [3]
Add a fourth record, `Discontinued`, and everything still compiles. [4] The post's author ran it and got an unhandled `InvalidOperationException` with the message "unreachable, promise". [4]
A union moves the list into the declaration. [5] A switch over `Pet`, with a `Dog` arm and a `Cat` arm and no discard arm, draws no warning, because the compiler knows those two are the whole list. [6] Add `Discontinued` to a union and every switch without an arm for it reports CS8509, naming the uncovered pattern. [9]
What the post shows is narrow. Its examples are a three-case lookup, a two-case `Pet` and an int-or-Guid `UserId`, with output reported from the author's own run. [15] The result carries over to a codebase only where the switches have no discard arm. In the interface version the compiler required that arm [3], and the missed case compiled past it [4]. We'd expect the arm to silence CS8509 on a union switch too. The post does not test that.
The setup cost is small on the RC1 SDK. The author's check was `#error version`, which reported language version 15.0, and the snippets compiled with no `LangVersion` set. [12] The `UnionAttribute` and `IUnion` types the compiler relies on have been in the runtime since .NET 11 Preview 5. [13] RC1 itself shipped on September 8, 2026. [14]
What to watch
- Which union features Microsoft's docs still list as unimplemented, and whether they land between RC1 and the final .NET 11 release.
- Reports from codebases that convert marker-interface hierarchies, since the post tests only small example records.
- Whether a discard arm on a union switch silences CS8509, which the post does not test.
Clarity's read
What the record supports and how the coverage leans. The claims behind it follow.
Reality
- Evidence68
- Adoption
- Insufficient
- Hype gap−5
- Incentives
- Insufficient
- Confidence62
Claim ledger
Ranked by verification strength, evidence, and original report placement.
- [1]
C# 15 adds union types, declared in the form: union LookupResult(Found, NotFound, Invalid);
- [2]
Union types ship with .NET 11, which is at Release Candidate 1: go-live licensed, but not final yet.
- [3]
With a marker interface, the compiler cannot know that only three types implement the interface, so it demands a _ arm, and the developer writes a throw for a case that 'can't happen'.
- [4]
When a teammate adds a Discontinued record implementing the marker interface, everything compiles; when the author ran it, the result was an unhandled System.InvalidOperationException with the message 'unreachable, promise'.
- [5]
A union names a closed set of types and a value of the union is exactly one of them; any case type converts to the union implicitly, and pattern matching looks straight through it.
- [6]
A switch expression over union Pet(Cat, Dog) with only a Dog arm and a Cat arm and no _ arm produces no warning, because the compiler knows Cat and Dog are the whole list.
- [7]
Assigning anything else to a union is a compile error: Pet p = "a string"; gives CS0029: Cannot implicitly convert type 'string' to 'Pet'.
- [8]
Union case types do not need a shared base class or interface, and they do not even need to be the developer's own types.
- [9]
When Discontinued is added to the union, the build reports warning CS8509 (the switch expression does not handle all possible values of its input type; the pattern 'Discontinued' is not covered), and the warning shows up at every switch that missed the new case.
- [10]
With TreatWarningsAsErrors on, the CS8509 warning is a failed build instead of a 2 AM page.
- [11]
A union UserId(int, Guid) works with a switch over int n and Guid g arms, mixing types that could not be given a common base.
- [12]
On the .NET 11 RC1 SDK, #error version reports Language version: 15.0, and every snippet in the post compiled with no LangVersion set.
- [13]
The UnionAttribute and IUnion types the compiler relies on are in the runtime starting with .NET 11 Preview 5.
- [14]
.NET 11 RC1 shipped on September 8, 2026 with a go-live support license, so it is allowed in production, though it is still a release candidate and not the final release.
- [15]
The post's examples are a three-case LookupResult, a two-case Pet and an int-or-Guid UserId, with program output reported from the author's own run.
- [16]
Microsoft's docs note that some parts of the union proposal are not implemented yet.
Get this ledger inside ChatGPT. Ask about any of our stories and the answer brings its figure, each claim’s verdict and the sources. Free, no account.
Sources
1 independent publisher whose own reporting we read for this story.
- dev.toStop Writing _ => throw for Cases That Can't Happen. C# 15 Has Unions
1 article · October 13, 2026
Topics and entities
Follow any of these and your For You feed starts watching them — no settings page required.