- NET 11 sdk (preview)
- Vscode insiders version
- C# extension in Vscode updated to preview version
- C# devkit extension in Vscode updated to preview version
.csproj must be set to preview
Example .csproj :
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net11.0</TargetFramework>
<LangVersion>preview</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
The Github repo for the code in this article is here:
C# Unions are coming in C# 15 and .NET 11. It seems like listing up the case types still needs you to use reflection. Probably we will see union types get more features in later C# versions, they are in C# 15 quite a new thing to C# and the language team of C# spent quite a few year to get it to work with the language and CLR. Shown below is an extension method that uses reflection in C# to discover the union case types. It is based that a union always implements the interface IUnion , often implicitly via just using the union keyword - in case it is not a Manual Union. By inspecting the mandatory constructors of the union and retrieving the first parameter's type , we can get the types of the union.
UnionExtensions.cs
/// <summary>
/// Provides reflection-based helpers for C# unions.
/// </summary>
public static class UnionExtensions
{
/// <summary>
/// Retrieves the union case types from generic type reference (compile-time checked)
/// </summary>
/// <typeparam name="TUnion">The union type to inspect.</typeparam>
/// <returns>The distinct case types exposed by the union constructors.</returns>
public static IReadOnlyList<Type> GetCaseTypes<TUnion>() where TUnion : IUnion
{
return GetUnionCaseTypes(typeof(TUnion));
}
/// <summary>
/// Retrieves the union case types from a runtime type reference.
/// </summary>
/// <param name="t">The union type to inspect.</param>
/// <returns>The distinct case types exposed by the union constructors.</returns>
public static IReadOnlyList<Type> GetUnionCaseTypes(this Type t){
if (t == null || !typeof(IUnion).IsAssignableFrom(t)){
return Array.Empty<Type>(); //guard against null or non-union types, just return an empty result in this case
}
return t
.GetConstructors(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance)
.Where(constructor => constructor.GetParameters().Length == 1)
.Select(constructor => constructor.GetParameters()[0].ParameterType)
.Distinct()
.ToArray();
}
}
The method below does not throw an exception in case the type t is null or a type that is not assignable from IUnion, i..e not a type that is a union type. Instead it just returns an empty array.
Although .NET 11 is expected to be shipped in November this year (2026), it does not seem like an easy way to get the case types of a union can be easily done without such a helper method, there will probably not be a language specific way of retrieving the case types of the union.
Below is sample code demonstrating the usage of the union case type listing helper extension method shown above. First off, defining a union type that has four records and also common behavior, adding a description expression bodied method.
public record Dog(string Name);
public record Cat(int NumberOfLives, string? Name = default);
public record Parrot(bool WantsCrackers, string? Name = default);
public record GoldFish(bool MakesBubbles, string? Name = default);
union Pet(Dog, Cat, Parrot, GoldFish)
{
public string Description => this switch
{
Cat cat => $"{cat.Name} says: Meow! I got {cat.NumberOfLives} lives left 🐈",
Dog dog => $"Bark Bark! {dog.Name} says! 🦴 🐕",
Parrot parrot => $"🦜Squawk! {parrot.Name} says: {(parrot.WantsCrackers ? "I want crackers!" : "Give us a kiss!")}",
GoldFish goldFish => $"🐠 {goldFish.Name} says: Blub Blub! {(goldFish.MakesBubbles ? "I make bubbles!" : "I don't make bubbles!")}"
};
}
The demo code below then makes use of the Pet union type defined and using the extension helper method. Please note the usage of the method that uses the parameterless method GetCaseTypes that is static compliler checked via the passed in type argument and the use of the GetUnionCaseTypes, that is runtime based where the type is passed in to the method. As shown in the GetUnionCaseTypes, erroneous usage is guarded. In case a wrong type is passed in and is not a union type which implements IUnion, an empty array is returned.
using System.Runtime.CompilerServices;
/// <summary>
/// Demonstrates discovering the case types of a C# union.
/// </summary>
public class Union1Demo
{
/// <summary>
/// Runs the union case discovery and pattern-matching examples.
/// </summary>
public static void RunDemo()
{
Console.WriteLine($"Pet union cases: {string.Join(", ", UnionExtensions.GetCaseTypes<Pet>().Select(type => type.Name))}");
var somePets = new Pet[]{
new Dog("Rex"),
new Cat(7, "Whiskers"),
new Parrot(true, "Polly"),
new GoldFish(true, "Timmy")
};
foreach (var pet in somePets)
{
Console.WriteLine(pet.Description);
}
Console.WriteLine();
Console.WriteLine("Listing all pet union case types:\n-----------------------------------------");
foreach (var caseType in typeof(Pet).GetUnionCaseTypes())
{
Console.Write($"* {caseType.FullName}");
Console.WriteLine($" with props: {string.Join(", ", caseType.GetProperties(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public).Select(p => p.Name))}");
}
}
}
The output of running the demo code is shown below:
Pet union cases: Dog, Cat, Parrot, GoldFish
Bark Bark! Rex says! 🦴 🐕
Whiskers says: Meow! I got 7 lives left 🐈
🦜Squawk! Polly says: I want crackers!
🐠 Timmy says: Blub Blub! I make bubbles!
Listing all pet union case types:
--------------------------------
* Dog with props: Name
* Cat with props: NumberOfLives, Name
* Parrot with props: WantsCrackers, Name
* GoldFish with props: MakesBubbles, Name