Convert OneOf Code to Native C# Unions
Paste your OneOf<T0, T1, ...> type declarations and .Match()/.Switch() call sites. Get back the equivalent native union declaration and switch expression code.
How OneOfToUnion works
- 1
Paste your OneOf code
Include the method signature with OneOf<T0, T1, ...> as the return type. Optionally include .Match() or .Switch() call sites from the same method.
- 2
Type parameters are extracted
The tool parses the generic type arguments from OneOf<...>, correctly handling nested generics like Success<User>.
- 3
Union declaration is generated
The generic type parameters become the case types in a union declaration. The union name is derived from the method name.
- 4
Call sites are converted
Each .Match() lambda becomes a switch expression arm with the corresponding type pattern. .Switch() calls are converted similarly.
- 5
Boxing warnings are flagged
If any case type is a known value type (int, DateTime, Guid, etc.), the tool warns that boxing will occur when mixing value and reference types in the same union.
What the output means
Union declaration
The equivalent native union type. This replaces the OneOf<...> return type. Declare it once at namespace or class scope, then use the union name as the return type of your method.
Switch expression
The equivalent of your .Match() or .Switch() call site, written as a standard C# switch expression with type-pattern arms. Each arm maps to one lambda from the original call.
Case type badges
Each case type is shown as a badge. Value types (int, DateTime, Guid, etc.) are highlighted in amber to flag potential boxing.
Boxing warning
Shown when your union mixes value types and reference types. Value type cases get boxed into the union's internal object? storage, which adds heap allocation and GC pressure in hot paths.
Supported input patterns
| Pattern | Example |
|---|---|
| Method signature with OneOf return type | public OneOf<Receipt, Error> PlaceOrder(int id) { ... } |
| .Match() call with lambdas | result.Match(r => Ok(r), e => BadRequest(e.Message)) |
| .Match() with assignment | var response = result.Match(r => Ok(r), e => BadRequest(e.Message)); |
| .Switch() call (void) | result.Switch(r => Log(r), e => Console.WriteLine(e.Message)); |
| Nested generics | OneOf<Success<User>, NotFound, ValidationError> |
When to use this tool
| Scenario | What to paste |
|---|---|
| Bulk migration of OneOf usages | Each OneOf method signature + its call sites, one at a time |
| Verifying a manual conversion | The original OneOf code to check your hand-written union matches |
| Writing a migration PR | All OneOf usages in the file for consistent, mechanically-generated conversions |
| Learning the new syntax | Click 'Load example' to see a complete before/after conversion |
Three mistakes when migrating off OneOf
1. Migrating every call site in one commit
Native unions and the OneOf package coexist without conflict on .NET 11. Convert one type and its call sites, compile, run the tests, then move to the next. A single sweeping commit turns one compile error into fifty and makes the failure hard to attribute.
2. Assuming the arm order does not matter
.Match() dispatches on the generic argument position, so order is irrelevant. A switch expression matches arms top to bottom, so a broader type pattern placed above a narrower one silently shadows it. If two case types share a base class, put the derived type first.
3. Ignoring the boxing warning on struct case types
When a case type is a struct, matching it through a union can box the value. In a hot path that allocation shows up in a profile. The converter flags these so you can decide whether the clarity is worth the allocation, rather than discovering it later under load.
Frequently Asked Questions
How do I convert OneOf to a native C# union?
Paste your OneOf<T0, T1, ...> method signature into this tool. It generates the equivalent union type declaration. If you also paste .Match() or .Switch() call sites, it converts those to switch expressions with the correct case type patterns.
The conversion is a starting point. Always review and compile the output before committing, since the tool uses pattern matching rather than a full C# parser.
Does OneOf still work with .NET 11?
Yes. The OneOf NuGet package continues to work on .NET 11. Native union types are a new language feature, not a replacement that removes OneOf compatibility. You can migrate incrementally, converting individual usages to native unions while keeping the rest on OneOf.
What is the syntax difference between OneOf.Match and a union switch expression?
OneOf's .Match() takes a lambda parameter for each generic type argument. The native union equivalent is a C# switch expression with pattern-matching arms:
// OneOf .Match()
result.Match(
success => Ok(success.Value),
error => BadRequest(error.Message)
);
// Native union switch expression
result switch
{
Success s => Ok(s.Value),
Error e => BadRequest(e.Message),
};The switch expression uses standard C# pattern matching syntax. Each arm specifies a type pattern, optionally binding a variable, followed by the expression to evaluate.
Does the tool handle generic case types like Success<T>?
Yes. The parser correctly handles nested generic type arguments in OneOf declarations. OneOf<Success<User>, NotFound, ValidationError> is parsed as three case types: Success<User>, NotFound, and ValidationError.
Is my code sent to any server?
No. All parsing and conversion runs entirely in your browser using JavaScript string pattern matching. No code is sent anywhere, no cookies are set, and the tool works offline once the page has loaded.
Does the compiler still catch a missing case after converting?
It depends on how the switch expression is written. With OneOf, .Match() forces you to supply a lambda for every type argument, so a missing case is a compile error. A native union switch expression only warns when the compiler can prove the arms are not exhaustive.
If you add a case type to the union later, add it to the switch arms at the same time. Leaving a bare discard arm hides the gap:
// Hides a missing case: the discard swallows new union members
result switch
{
Success s => Ok(s.Value),
_ => throw new InvalidOperationException(),
};
// Preferred: every case named, so adding one to the union breaks the build
result switch
{
Success s => Ok(s.Value),
NotFound => NotFound(),
ValidationError e => BadRequest(e.Message),
};What will the converter not handle correctly?
The tool matches patterns in text rather than building a full C# syntax tree, so a few constructs need a manual pass after conversion.
.Match()calls split across deeply nested expressions, where the closing parenthesis is ambiguous to a pattern matcher.- Lambda bodies containing a nested
.Match()on a second OneOf value: convert the innermost call first, then rerun the tool. TryPickT0,AsT1, and the other index-based accessors, which have no direct union equivalent and need rewriting as pattern matches by hand.- Custom implicit conversion operators declared on your own OneOf-derived types.
In every case the output is still a useful starting point. Compile after converting, and treat any error as a spot that needed a human decision rather than a bug in the conversion.