Bring exhaustive pattern matching to C# enums and unions with zero boilerplate.
MatchGenerator is a Roslyn source generator that creates Match extension methods for your enums and discriminated-union-like types, enabling concise, expressive, and compile-time safe branching.
- Generate
Matchextension methods for enums and unions - Exhaustive by design (no missing cases)
- Attribute-driven (opt-in per type)
- Supports generics (
Match<U>) - Respects effective accessibility
- Zero runtime cost (pure source generation)
dotnet add package Aigamo.MatchGeneratorusing Aigamo.MatchGenerator;
[GenerateMatch]
public enum Gender
{
Male = 1,
Female,
}using Aigamo.MatchGenerator;
[GenerateMatch]
abstract record MaritalStatus;
sealed record Single : MaritalStatus;
sealed record Married : MaritalStatus;
sealed record Divorced : MaritalStatus;
sealed record Widowed : MaritalStatus;var message = gender.Match(
onMale: () => "male",
onFemale: () => "female"
);var message = maritalStatus.Match(
onSingle: x => "single",
onMarried: x => "married",
onDivorced: x => "divorced",
onWidowed: x => "widowed"
);var message = gender switch
{
Gender.Male => "male",
Gender.Female => "female",
_ => throw new UnreachableException(),
};var message = maritalStatus switch
{
Single x => "single",
Married x => "married",
Divorced x => "divorced",
Widowed x => "widowed",
_ => throw new UnreachableException(),
};var message = gender.Match(
onMale: () => "male",
onFemale: () => "female"
);- More concise
- More readable
- No default case required
- Compile-time safety
All cases must be handled.
If a new enum value or union type is added:
public enum Gender
{
Male = 1,
Female,
Other,
}or
sealed record Separated : MaritalStatus;Existing Match calls will fail to compile until updated. This ensures no cases are missed.
internal static class GenderMatchExtensions
{
public static U Match<U>(
this Gender value,
Func<U> onFemale,
Func<U> onMale
)
{
return value switch
{
Gender.Female => onFemale(),
Gender.Male => onMale(),
_ => throw new UnreachableException(),
};
}
}internal static class MaritalStatusMatchExtensions
{
public static U Match<U>(
this MaritalStatus value,
Func<Divorced, U> onDivorced,
Func<Married, U> onMarried,
Func<Single, U> onSingle,
Func<Widowed, U> onWidowed
)
{
return value switch
{
Divorced x => onDivorced(x),
Married x => onMarried(x),
Single x => onSingle(x),
Widowed x => onWidowed(x),
_ => throw new UnreachableException(),
};
}
}- Introducing C# Source Generators - .NET Blog
- roslyn/docs/features/source-generators.cookbook.md at main · dotnet/roslyn
- roslyn/docs/features/incremental-generators.cookbook.md at main · dotnet/roslyn
- Domain Modeling Made Functional: Tackle Software Complexity with Domain-Driven Design and F# by Scott Wlaschin
- It Seems the C# Team Is Finally Considering Supporting Discriminated Unions - DEV Community
- salvois/DiscriminatedOnions: A stinky but tasty hack to emulate F#-like discriminated unions in C#