One of the most notable additions in the latest C# previews is union types, a language feature that allows a value to be one of a fixed set of types. Union types aim to make APIs more expressive, improve compile-time safety, and integrate seamlessly with C#’s existing pattern matching capabilities.
What Is a Union Type?
A union type represents a value that can be exactly one of several case types. Unlike inheritance or interfaces, the set of allowed types is closed and declared explicitly.
Union types are declared using the new union keyword:
public union Pet(Cat, Dog, Bird);This declaration creates a Pet union whose values can only be a Cat, Dog, or Bird. The compiler understands all possible cases and uses that information during pattern matching and exhaustiveness analysis.
Why Add Union Types?
According to the Microsoft documentation, union types are intended for scenarios where a value must be one of a known set of alternatives.
Some common examples include:
- Result-or-error returns, where a method returns either a success value or an error.
- Message or command processing, where only a fixed set of message types exists.
- Replacing marker interfaces or abstract base classes that exist solely for pattern matching rather than shared behavior.
Because the compiler knows every possible case, it can warn developers when a new case is introduced but existing switch expressions haven’t been updated.
How Union Types Differ from Existing C# Types
The documentation highlights several important distinctions:
- Unlike a class or struct, a union doesn’t introduce new data members. Instead, it groups existing types into a closed set.
- Unlike an interface, a union is closed. No additional case types can be added later.
- Unlike a record, a union is focused on representing alternatives rather than generating equality, cloning, or deconstruction functionality.
Built-In Implicit Conversions
One convenient aspect of union types is that every case type automatically converts to the union type.
For example:
Pet pet = new Dog("Rex");
Pet pet2 = new Cat("Whiskers");There’s no need to explicitly construct the union—the compiler generates the necessary conversion members automatically.
Pattern Matching Becomes More Powerful
Union types integrate directly with C# pattern matching.
var name = pet switch{Dog d => d.Name, Cat c => c.Name, Bird b => b.Name};Notice that the patterns match the underlying case types (Dog, Cat, and Bird) rather than the union itself. The compiler automatically “unwraps” the union during pattern matching.
Exhaustive Pattern Matching
One of the biggest advantages of union types is exhaustive matching.
Since the compiler knows every possible case, it can determine whether a switch expression handles them all. If a case type is missing, the compiler issues a warning.
Unlike many existing C# switch expressions, you don’t need to include a discard (_) pattern simply to satisfy the compiler when all union cases are already covered.
Nullability Support
Union types also integrate with C#’s nullable reference analysis.
The compiler tracks the null state of a union’s Value property and can require a null pattern when appropriate. The goal is consistent nullability analysis alongside exhaustive pattern matching.
Generic Union Types
Union types fully support generics.
For example, the documentation demonstrates an Option<T> type:
public record class None;
public record class Some<T>(T Value);
public union Option<T>(None, Some<T>);The documentation also shows that built-in value types can be used directly:
public union IntOrString(int, string);This allows unions to represent both reference types and value types.
Custom Union Types
Although the union keyword generates a default implementation, developers can also create custom union types manually.
A custom union is any class or struct marked with the [Union] attribute that follows the required pattern:
- A
[Union]attribute on the type - One or more public single-parameter constructors (each defining a case type).
- A public
Valueproperty of typeobject?.
This approach allows scenarios such as:
- Class-based unions
- Custom storage strategies
- Interoperability with existing types
Generated Implementation
The documentation explains that a declaration like:
public union Pet(Cat, Dog, Bird);is compiled into a struct conceptually equivalent to:
[Union]
public struct Pet : IUnion
{
public Pet(Cat value) => Value = value;
public Pet(Dog value) => Value = value;
public Pet(Bird value) => Value = value;
public object? Value { get; }
}This generated implementation provides the constructors, implicit conversions, and runtime support required for the language feature.
Preview Status and Current Limitation
The runtime includes these types beginning with .NET 11 Preview 5.
Final Thoughts
Union types introduce a new way to model values that belong to a fixed set of alternatives. Rather than relying on inheritance hierarchies or marker interfaces, developers can explicitly declare all valid case types and let the compiler enforce exhaustive handling through pattern matching.
Although still in preview, the feature integrates naturally with modern C# capabilities—including pattern matching, generics, and nullable reference types—and provides a more expressive way to design APIs where multiple, well-defined outcomes are expected.
Reference:
https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/union