Skip to content

Union types C# 15.0correctnessnew scenarios

Declare a type that holds exactly one of several case types, with exhaustive switch checking.

Sometimes a value is one of a small, fixed set of unrelated types: a pet is a cat, a dog or a bird, and nothing else. Before C# 15 you had to model this with a shared base class or interface (which the case types might not be able to adopt) or fall back to object and lose all compiler help.

C# 15 introduces union types using the union keyword. A union lists its case types, gets implicit conversions from each of them, and lets the compiler check that a switch expression handles every case. It is a union of existing types rather than a tagged union: patterns match against the value held inside the union, so you switch on Dog or Cat directly.

Code ​

csharp
Pet pet = new Dog("Rex");

string name = pet switch
{
    Dog d => d.Name,
    Cat c => c.Name,
    Bird b => b.Name,
    // No default arm needed, the compiler knows these are all the cases
};

public record class Cat(string Name);
public record class Dog(string Name);
public record class Bird(string Name);

public union Pet(Cat, Dog, Bird);
csharp
// Nothing stops other types being passed and the compiler can't check coverage
object pet = new Dog("Rex");

string name = pet switch
{
    Dog d => d.Name,
    Cat c => c.Name,
    Bird b => b.Name,
    _ => throw new InvalidOperationException("Unexpected pet type"),
};

public record class Cat(string Name);
public record class Dog(string Name);
public record class Bird(string Name);

Notes ​

  • Case types do not need to share a base type or interface
  • A union declaration compiles to a struct that stores its contents in a single object? Value property, so value-type cases such as int are boxed
  • A default union has a null Value, so when the compiler can't rule that out it warns about an unhandled null even if every case type is covered
  • See also closed hierarchies for exhaustiveness across a class hierarchy

More information ​

Powered by Intracia