NDepend Blog

Improve your .NET code quality with NDepend

C# 15 Unions

May 25, 2026 9 minutes read

C# Unions

A C# union -also called a discriminated union, tagged union or sum type- is a type whose value is exactly one of a fixed, closed set of case types. Switch over it and the compiler checks that every case is handled, with no default arm needed. C# 15 ships this as the union keyword.

In an August 2025 video, Mads Torgersen -lead designer for the C# Programming Language- outlined the C# team’s plans for C# unions. The team was aiming at potentially shipping this feature with the C# 15 release in November 2026. The video is here and he talks about union from 1:02:25 till 1:10:30.

Update April 2026: What was a possibility nine months ago is now real. Unions shipped as a preview in .NET 11 Preview 2. We have updated the post below to reflect what actually landed in the language. Most of the original plan went in unchanged. A few details ended up nicer than what Mads described in the video.

The short version:

  • C# unions arrive with C# 15 / .NET 11. GA is expected November 2026, and a first preview already landed in .NET 11 Preview 2 (April 2026).
  • The union keyword composes existing types into a closed set. No shared base class or interface required.
  • Pattern matching is exhaustive. Cover every case type and the switch compiles with no default arm.
  • Under the hood a union is a record struct holding one object? Value. Value types get boxed unless you hand-write the layout.
  • Unlike the OneOf NuGet package, exhaustiveness is enforced by the compiler, not at runtime.

To try it today, still target net10.0 and set the language version to preview in the csproj:

As we will explain later you need also to append this code in your project:

C# Union Syntax: The Plan That Shipped

Mads originally introduced this code to illustrate the plan. The recently shipped syntax matches it exactly:

This code feels very idiomatic to C#. Here are some remarks:

  • Pet wraps the existing types Dog, Cat, and Bird. Once declared, you no longer repeat the constituent types. Pet already carries that information.
  • The first line, Pet pet = new Dog("Rover");, compiles because the compiler emits an implicit conversion from each case type to the union. The compiler also rejects any value of an unlisted type. So Pet pet = new Shark(20); fails to compile.
  • Unlike some languages such as F#, C# does not let you declare the case types inside the union. You declare them on their own. For comparison, in F# a union looks like the snippet below, where Car, Truck, and Bicycle belong to the Vehicle union. They act as tags for the various cases rather than as types of their own. This is why functional languages use the names discriminated union or tag union. The C# team picked a different model, closer to a structural union or type union.

  • A key benefit shows up in pattern matching. The compiler knows Pet only ever holds a Dog, Cat, or Bird. So a switch expression over Pet compiles without warning once all three arms are present. No _ discard, no default arm. That is the whole point of a closed set of types.
  • Extend the union later with Shark, and the compiler warns every switch on Pet that misses the new case. This makes Pet a closed union type. The compiler fixes its set of possibilities and enforces them. Class inheritance works the opposite way. You can add new subclasses without breaking existing code, thanks to polymorphism on virtual members. This is the Open-Close Principle (OCP): open for extension, closed for modification. In a word OCP applies to class inheritance but not to unions.

How C# Unions Work Behind the Scenes

The C# team turned union types into a record struct with a single read-only object? Value property. The design feels simple and powerful. In C# virtually any type fits into an object reference: class, struct, interface, delegate, enumeration, primitive types… but not unmanaged pointers for example.

The Default Value and the Null Arm

A union is a struct, so it has a default value where the underlying Value is null. The compiler treats that as a real case you might have to handle:

Consider a union whose case types are all non-nullable. The compiler won’t force you to write a null arm. It just warns the way it does for any other nullable. As soon as one case type is itself nullable (Bird?, int?, etc.), you must add a null arm for exhaustiveness.

The UnionAttribute and IUnion Contract

The UnionAttribute and the IUnion interface both live in System.Runtime.CompilerServices. .NET 11 Preview 4 does not yet ship them in the BCL. To play with the feature you have to declare them yourself:

Later previews will bring them into the runtime. The important point: this is a public contract. Any type that carries [Union], exposes one single-parameter public constructor per case type, and implements IUnion behaves as a union. The compiler picks it up for pattern matching and exhaustiveness. In other words, the union keyword is sugar over a pattern that anyone can implement by hand. We will come back to that.

Adding a Body to a Union

A union declaration goes beyond its case list. It can also carry a body. That body is handy when you want helper methods that hide the switch from callers:

A few things worth pointing out in that snippet:

  • Unions can be generic. OneOrMore<T> is a single declaration that works for any T.
  • Inside the body, the compiler treats Value as maybe-null. That matches reality: a default OneOrMore<T> has a null Value.
  • The combination “single item or collection of items” is a real pain point in API design. Until now the choices were ugly. Take IEnumerable<T> everywhere and force callers to wrap. Or take object and write defensive code. Or define overloads. With OneOrMore<T> the API tells the truth about what it accepts. The helper hides the dispatch:

This kind of small union with a helper is, I suspect, where most real-world usage will happen. Not the textbook Pet example.

Could C# Unions be Smart for Value Type Only Unions?

Storing everything in an object? has a trade-off. The runtime boxes any value type listed in the union, such as Cat, the moment you write it into Value. The garbage collector then has to manage that box, which adds a small performance overhead.

A smarter implementation could optimize storage for value types that share the same memory footprint. The runtime could also allocate space based on the largest value type in the union. Smaller types would then carry padding overhead. Either approach would significantly complicate things. At 1:08:20 Mads explains that the C# team will let you define the layout of a value-type-only union by hand. That is exactly the door [Union] + IUnion opens. You write a custom record struct and tag it with [Union]. Inside, you store the cases as discriminated fields in an explicit layout, not as a boxed object?. The compiler still checks exhaustiveness on switches over your type. But you keep control of the allocation story.

HasValue and TryGetValue

To make this work the team adds two opt-in members the compiler recognises on a custom union: HasValue and a generic TryGetValue<T>(out T value). When both are present, the compiler uses them instead of going through Value. A switch then never boxes.

I had wished for this kind of escape hatch in the original post. Good to see the team thought about it. I still believe unions made solely of value types form a common scenario. Think parser implementation, messaging protocol, financial instruments. Avoiding allocations there matters. In the same vein C# 13 eliminated object allocations for params collections.

Reading Bytes with MemoryMarshal

Modern C# and MemoryMarshal make it easy to cast a range of bytes into any value type instance. Handy when you build such a hand-written value-only union:

What Is Not Yet in C# 15 Unions

The team flags a few items as not done yet:

  • Union member providers. Today, if every case type in a Pet exposes a Name property, you still need a switch to read it. The team plans to let you write pet.Name directly when every case carries the property. Useful, and clearly inspired by F# active patterns and Kotlin sealed-class member access.
  • Uninitialized-field analysis. Today the compiler does not warn when you forget to assign a union field in a constructor. Even though default means a null Value.
  • Closed hierarchies and closed enums. Not part of the union feature itself. The team is openly considering a closed modifier on class hierarchies and a closed flavour of enum. Both would give you the same exhaustiveness guarantee unions give. Useful where unions are not the right modelling tool.

Visual Studio support already lives in the Insiders build. Regular Visual Studio will get it in a future update.

C# Unions vs OneOf, Class Hierarchies and Enums

Before unions, C# developers reached for the OneOf NuGet package, an abstract base class, or an enum plus a payload. Here is how the native union compares:

Approach Closed set Compiler exhaustiveness No inheritance needed
C# 15 union Yes Yes Yes
OneOf<...> NuGet Yes No (runtime) Yes
Class / interface hierarchy No (open) No No
enum Yes Partial n/a

The win is the middle column. Only the union keyword turns “did I handle every case?” into a compile-time check rather than a runtime habit.

C# Unions FAQ

What is a C# union type?

A union is a type whose value is exactly one of a fixed set of case types: classes, structs, records or primitives. The compiler enforces that set and checks that every switch handles all cases. Other names for the concept are discriminated union, tagged union and sum type.

When are C# unions released?

They are scheduled for C# 15 with .NET 11, GA expected November 2026. A first preview shipped in .NET 11 Preview 2 in April 2026, behind <LangVersion>preview</LangVersion>.

How do C# unions differ from F# discriminated unions?

F# declares the case tags inside the union. C# composes existing standalone types instead, so its model is closer to a structural or type union than to a tag union. Both give exhaustive pattern matching.

Do C# unions box value types?

Yes. The generated union stores its content in a single object? property, so a struct case is boxed. You avoid that by hand-writing a [Union] record struct with HasValue and TryGetValue members.

How do I use C# unions today?

Set the preview language version, then declare UnionAttribute and IUnion yourself in System.Runtime.CompilerServices until a later preview adds them to the BCL.

Are C# unions better than the OneOf library?

They cover the same need but at the language level. OneOf checks cases at runtime, while the union keyword gives compiler-enforced exhaustiveness and cleaner pattern matching.

Conclusion

The August 2025 prediction held up. Unions are coming in C# 15, and the syntax Mads showed on stage matches what shipped in Preview 2. Pattern matching ended up a touch nicer than expected, since you switch on the union directly rather than on its Value. Generic unions and union bodies turn the feature into something actually useful for everyday API design. The [Union] / IUnion escape hatch leaves room for the value-type-only optimisation that some of us were worried about.

The team also confirmed they will work with the .NET Core Base Class Library team. Many union types should ship alongside the language feature. The F# interop story remains open, though.

In 2024 we already wrote a blog post named C# Discriminated Union to explain the concept to C# developers: C# Discriminated Union: What’s Driving the C# Community’s Inquiries?. Hopefully, things are becoming clearer. We will keep updating this post as the feature evolves toward C# 15 GA in November 2026.

This article is brought to you by the team behind NDepend — a proven .NET static analysis tool for improving code maintainability, security, and overall quality. Whether you’re modernizing a legacy .NET application or starting fresh in C#, get started with your free full-featured trial today!

Leave a Reply

Your email address will not be published. Required fields are marked *