MatchGenerator
June 7, 2026 · View on GitHub
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.
Features
- Generate
Matchextension methods for enums and unions - Exhaustive by design (no missing cases)
- Attribute-driven (opt-in per type)
- Works with external types you don't own (via
[assembly: GenerateMatchFor(typeof(T))]) - Supports generics (
Match<U>) - Respects effective accessibility
- Zero runtime cost (pure source generation)
Getting Started
1. Install the package
dotnet add package Aigamo.MatchGenerator
2. Annotate your type
Enum example
using Aigamo.MatchGenerator;
[GenerateMatch]
public enum Gender
{
Male = 1,
Female,
}
Union example
using Aigamo.MatchGenerator;
[GenerateMatch]
abstract record MaritalStatus;
sealed record Single : MaritalStatus;
sealed record Married : MaritalStatus;
sealed record Divorced : MaritalStatus;
sealed record Widowed : MaritalStatus;
External type example
If the enum or union lives in another assembly — so you can't put [GenerateMatch] on it — target it by typeof with an assembly-level attribute instead:
using Aigamo.MatchGenerator;
[assembly: GenerateMatchFor(typeof(DayOfWeek))]
Then call Match exactly as you would on an annotated type:
var label = today.Match(
onMonday: () => "Mon",
onTuesday: () => "Tue",
onWednesday: () => "Wed",
onThursday: () => "Thu",
onFriday: () => "Fri",
onSaturday: () => "Sat",
onSunday: () => "Sun"
);
The generated method is placed in the target type's namespace and is internal to your assembly (a local convenience, not public API on a type you don't own). Repeat the attribute (it allows multiple) to target several types. This works for external enums, and for unions whose derived types are declared in your own code (cross-assembly derived types are not discovered).
If a target has nothing to match — it isn't an enum and has no derived types in your compilation — the generator reports AMG001 and skips it.
3. Use Match
Enum
var message = gender.Match(
onMale: () => "male",
onFemale: () => "female"
);
Union
var message = maritalStatus.Match(
onSingle: x => "single",
onMarried: x => "married",
onDivorced: x => "divorced",
onWidowed: x => "widowed"
);
Why use MatchGenerator?
Without MatchGenerator
Enum
var message = gender switch
{
Gender.Male => "male",
Gender.Female => "female",
_ => throw new UnreachableException(),
};
Union
var message = maritalStatus switch
{
Single x => "single",
Married x => "married",
Divorced x => "divorced",
Widowed x => "widowed",
_ => throw new UnreachableException(),
};
With MatchGenerator
var message = gender.Match(
onMale: () => "male",
onFemale: () => "female"
);
- More concise
- More readable
- No default case required
- Compile-time safety
Exhaustiveness Guarantee
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.
Generated Code (Example)
Enum
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(),
};
}
}
Union
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(),
};
}
}
References
- 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#