C# is getting union types, and I wanted to try them out as soon as possible. In this post, I’ll show how to get them working with the current .NET preview SDK, what the setup looks like, and why unions can be a better fit than inheritance or interfaces in some cases.
To try out unions, you need the preview version of the .NET 11 SDK. You can install it from the official .NET download page.
You also need to enable preview language features in your project file (and of course have .NET version set to 11):
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net11.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>preview</LangVersion>
</PropertyGroup>
</Project>
At the moment, the types are not fully wired up yet, so you also need to add a small polyfill to make unions work. This needs to be placed exactly in the System.Runtime.CompilerServices namespace:
namespace System.Runtime.CompilerServices
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false)]
public sealed class UnionAttribute : Attribute;
public interface IUnion
{
object? Value { get; }
}
}
One thing I ran into: I could not get top-level statements working. I had to switch to a normal Main method in my console app, because otherwise I kept getting an error about the missing entry point after adding the attribute. This might be fixed on later version of .NET, but as of today it was not working with top-level statements.
A simple union example
Here is a sample of union type. The union is defined with a union keyword and it lists the types it can “contain”. This union type represents a general vehicle, and the application only supports a fixed set of vehicle types (Car, Truck or an IMotorcycle). The supported types can be a class, record, struct, or interface type. I think in most of the cases you would just list class or record types in here, but I wanted to show that it also supports interfaces etc.
public union Vehicle(Car, Truck, IMotorcycle)
{
bool isMotorCycle => this is IMotorcycle motorcycle; // union can contain member variables
public void PrintIfIsMotorcycle() // and functions like a base class would
{
if (isMotorCycle)
{
Console.WriteLine($"This is a motorcycle.");
}
}
}
The implementing types in this sample are simple placeholders:
public record class Car
{
public int NumberOfDoors { get; set; }
}
public struct Truck
{
public int PayloadCapacity { get; set; }
}
public interface IMotorcycle
{
public bool HasSidecar { get; set; }
}
public class Motorcycle : IMotorcycle
{
public bool HasSidecar { get; set; }
}
Using the union
This is how the union works in practice:
public static void Main()
{
Car car = new Car { NumberOfDoors = 4 };
Truck truck = new Truck { PayloadCapacity = 1000 };
Motorcycle motorcycle = new Motorcycle();
ProcessVehicle(car);
ProcessVehicle(truck);
ProcessVehicle(motorcycle);
}
public static void ProcessVehicle(Vehicle vehicle)
{
switch (vehicle)
{
case Car car:
Console.WriteLine($"Processing a car with {car.NumberOfDoors} doors.");
break;
case Truck truck:
Console.WriteLine($"Processing a truck with {truck.PayloadCapacity} kg payload capacity.");
break;
case IMotorcycle motorcycle:
vehicle.PrintIfIsMotorcycle(); // call the union method
Console.WriteLine($"Processing a motorcycle with sidecar: {motorcycle.HasSidecar}");
break;
default: // This is for the null
Console.WriteLine("Unknown vehicle type.");
break;
}
}
Why not just inherit?
The point of a union is that it models a closed set of alternatives rather than a shared hierarchy. A base class is open-ended: other code can derive new subtypes later, so the compiler cannot know that you handled every case.
That is one of the main reasons unions are interesting. They give you a way to say, “this value is one of these known types,” and the compiler can help you reason about that set.
There is also another practical issue: .NET does not support multiple inheritance for classes, which can force you into awkward type designs. With unions, you can combine multiple supported shapes without locking yourself into one hierarchy.
Why not just use interfaces?
Interfaces are great, but they solve a different problem. A union represents a closed set of possible values. An interface represents a common shape or capability that any number of types can implement. If the types do not really share behavior, an interface often turns into a marker interface, and that is usually a sign that you are using it as a workaround. Unions are meant to solve exactly that kind of problem.
A simple rule of thumb is this:
- If your question is “what can this object do?”, use an interface.
- If your question is “which one of these known things is this value?”, use a union.
For example, IStreamSource means “anything that can provide a stream,” while Result(Success, Error) means “this value is exactly one of two outcomes.”
Boxing
One thing to keep in mind is boxing. In the current preview shape, union values may go through an object-based access path, which means value-type cases can end up boxed. That is perfectly fine for most of apps, but it is worth being aware of if you care about allocations or performance-sensitive code. The design is still evolving, so this part may improve as the feature matures.
Final thoughts
Unions feel like a useful addition to C#. They are especially interesting when you have a fixed set of domain types and want the compiler to help you handle them correctly.
For now, the feature is still preview, so the exact details may change. But even in this early form, it already shows why many developers have wanted this kind of type system support in C# for a long time.
If you want to follow the development of the Union feature, you can find the discussion from csharp lang GitHub repository
