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 aims at potentially introduce this feature with the C# 15 release in November 2026. This is not a commitment but rather a possibility—one that seems quite likely, given how straightforward the plan appears to be. The video is here and he talks about union from 1:02:25 till 1:10:30.
C# Unions Plan
Mads introduces this code to illustrate their plans for unions:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
Pet pet = new Dog("Rover"); var description = pet switch { Dog(var name) => name, Cat(var adjective) => $"A {adjective} cat", Bird bird => $"A {bird.Species}", }; public union Pet (Dog, Cat, Bird); public record class Dog(string Name); public record struct Cat(string Adjective); public record class Bird(string Species); public record class Shark(int Teeth); |
This code feels very idiomatic to C#. Here are some remarks:
- The type
Pet
is defined as a union of the existing typesDog
,Cat
, andBird
. Once declared, there’s no need to repeatedly specify the constituent types—Pet
fully encapsulates that definition. - The first line,
Pet pet = new Dog("Rover");
means that the compiler will know somehow there is an implicit conversion fromDog
toPet
, sincePet
is not a base class forDog
. - Unlike some languages such as F#, the types that make up a union in C# must be defined separately from the union itself. For comparison, in F# a union can be declared as follows, where
Car
,Truck
, andBicycle
are part of theVehicle
union. They can be seen as tags to represent the various cases, instead of being seen as type. This is why, in functional languages, they are called discriminated union or tag union, whereas in the approach chosen by the C# team, it is closer to a structural union or type union.
1 2 3 4 |
type Vehicle = | Car of model : string * year : int | Truck of model : string * year : int * payload : float | Bicycle of model : string |
- A key benefit is how exhaustiveness checking works with pattern matching in the
switch
clause. Since the compiler knows thatPet
can only ever be aDog
,Cat
, orBird
, aswitch
expression overPet
will not produce warnings once all cases are handled. - If we later extend the union by adding, for example,
Shark
, the compiler will warn (or error?) everyswitch
onPet
that doesn’t account for the new case. This makesPet
a closed union type: its set of possibilities is fixed and enforced by the compiler. This is in contrast to class inheritance, where new subclasses can be introduced without breaking existing code thanks to polymorphism applied 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.
C# Union Plan Behind the Scene
The C# team proposes implementing union types as a record struct
with a single read-only object? Value
property. This design is both simple and powerful, since in C# virtually any type can be represented as an object
reference: class, struct, interface, delegate, enumeration, primitive types… but not unmanaged pointers for example.
1 2 3 4 5 6 7 8 9 10 |
#region public union Pet (Dog, Cat, Bird); public partial record struct Pet : IUnion { public Pet(Dog value) => Value = value; public Pet(Cat value) => Value = value; public Pet(Bird value) => Value = value; public object? Value { get; } } #endregion |
Could C# Unions be Smart for Value Type Only Unions?
The trade-off is that value types included in the union, such as Cat
, will be boxed when stored in the Value
property. The object used to box the value must then be managed by the garbage collector, introducing a small performance overhead.
A more sophisticated implementation of unions could optimize storage for value types that share the same memory footprint (i.e., represented by the same number of bytes). Alternatively, the runtime could allocate space based on the largest value type in the union, with smaller types incurring padding overhead. However, either approach would significantly complicate the implementation. At 1:08:20 Mads explains that the C# Team will provide a way to manually define the layout of your value type only union.
I wish this could be transparently improved. A method T ValueAs<T>() where T : struct
could be added to Pet
. In the same vein C# 13 eliminated object allocations for params
collections. I believe that unions composed solely of value types are likely to be a common scenario, like for example parser implementation or messaging protocol.
Let’s mention that modern C# and MemoryMarshal
makes it easy to cast a range of byte into any value type instance:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
using System.Diagnostics; using System.Runtime.InteropServices; Span<byte> bytes8 = stackalloc byte[8] { 2, 0, 0, 0, 1, 0, 0, 0 }; // Convert first 4 bytes to uint uint value32 = MemoryMarshal.Read<uint>(bytes8); Debug.Assert(value32 == 2); // Convert all 8 bytes to ulong ulong value64 = MemoryMarshal.Read<ulong>(bytes8); Debug.Assert(value64 == 4294967298); // Convert all 8 bytes to MyStruct ref MyStruct myStruct = ref MemoryMarshal.AsRef<MyStruct>(bytes8); Debug.Assert(myStruct.X == 2); Debug.Assert(myStruct.Y == 1); [StructLayout(LayoutKind.Sequential)] struct MyStruct { public uint X; public uint Y; } |
Pattern Matching switch over C# Union
Based on the C# team’s plan, enabling pattern-matching switch
on a C# union is straightforward. A call to the Value
property must be inferred by the compiler.
1 2 3 4 5 |
var description = pet.Value switch { Dog(var name) => name, Cat(var adjective) => $"A {adjective} cat", Bird bird => $"A {bird.Species}", }; |
Conclusion
Again, Mads did not promise that unions will be part of C# 15 in November 2026. He explained that work will begin once C# 14 reaches general availability in November 2025 and hopefully they will make it.
Also he mentioned that they will collaborate with the .NET Core Base Class Library team so that many union types will already be available when this language feature is released
At the end of the talk, someone asked about interoperability between F# unions and C# unions. Since they are fundamentally different (see the explanation above on F# discriminated unions vs. C# type unions), the interoperability model is not straightforward. Nevertheless the team plans to work on it. Mads also added that they might allow defining the types that make up a C# union directly within the union’s scope, similar to F#.
In 2024 we already wrote a blog post named C# Discriminated Union to explain in details the concept to C# developers: C# Discriminated Union: What’s Driving the C# Community’s Inquiries?. Hopefully, things are becoming clearer, and we will update this post with the latest progress from the C# team on this topic.
This article is provided by the team behind NDepend. Whether you are maintaining a large legacy .NET project or starting a new C# application, NDepend helps improve maintainability, security, and code quality. A free full-featured trial is available here for evaluation.