UnionGenerator.OneOfSourceGen
0.1.0
dotnet add package UnionGenerator.OneOfSourceGen --version 0.1.0
NuGet\Install-Package UnionGenerator.OneOfSourceGen -Version 0.1.0
<PackageReference Include="UnionGenerator.OneOfSourceGen" Version="0.1.0"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
<PackageVersion Include="UnionGenerator.OneOfSourceGen" Version="0.1.0" />
<PackageReference Include="UnionGenerator.OneOfSourceGen"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
paket add UnionGenerator.OneOfSourceGen --version 0.1.0
#r "nuget: UnionGenerator.OneOfSourceGen, 0.1.0"
#:package UnionGenerator.OneOfSourceGen@0.1.0
#addin nuget:?package=UnionGenerator.OneOfSourceGen&version=0.1.0
#tool nuget:?package=UnionGenerator.OneOfSourceGen&version=0.1.0
UnionGenerator.OneOfSourceGen
Convert OneOf types to UnionGenerator unions with zero reflection. Compile-time code generation creates ultra-fast adapters perfect for high-frequency conversion scenarios.
❓ Why This Package?
The Problem
Reflection-based conversion adds latency that compounds at scale:
// ❌ OneOfCompat: Reflection overhead (~15-65 µs per call)
// In an API with 10,000 req/sec, each with 5 conversions:
// 10,000 × 5 × 50 µs = 2.5 seconds overhead per second!
var result = OneOfCompat.FromT0<Result<User, Error>, User, Error>(user);
// ❌ OneOfExtensions: Faster but still uses reflection (~10-35 µs)
var result = oneOfValue.ToGeneratedResult<Result<User, Error>, User, Error>();
The Solution
// ✅ OneOfSourceGen: Compile-time generated (0 reflection, ~10-50 ns)
// Same 10,000 req/sec with 5 conversions:
// 10,000 × 5 × 30 ns = 1.5 milliseconds per second (negligible!)
var result = oneOfValue.FromOneOf<Result<User, Error>, User, Error>();
// The generator wrote this at compile time (pure C#):
public static Result<User, Error> FromOneOf(this OneOf<User, Error> oneOf)
=> oneOf.Match(
user => Result<User, Error>.Ok(user),
error => Result<User, Error>.Error(error)
);
Impact: ~3,000x faster conversion with zero runtime overhead.
🚀 Quick Start (2 minutes)
1. Install
dotnet add package UnionGenerator.OneOfSourceGen
2. Define Your Union
using UnionGenerator.Attributes;
[GenerateUnion]
public partial class Result<T, E>
{
public static Result<T, E> Ok(T value) => new OkCase(value);
public static Result<T, E> Error(E error) => new ErrorCase(error);
}
3. Convert OneOf Values (Generated Code)
using OneOf;
OneOf<User, string> oneOfResult = GetOneOfResult();
// Generator creates this automatically:
// ResultOneOfAdapter.FromOneOf<User, string>(oneOfResult)
var result = oneOfResult.FromOneOf<Result<User, string>, User, string>();
That's it! The generator automatically creates zero-reflection adapters at compile time.
📚 Features
✅ Compile-Time Code Generation
No reflection at runtime. All adapters generated at compile time.
✅ Zero-Reflection Performance
Adapter methods are pure generated C#, with performance equivalent to hand-written code.
✅ Safe Try-Pattern Support
Both throwing (FromOneOf) and non-throwing (TryFromOneOf) variants.
✅ Compile-Time Diagnostics
Clear error messages (UG2001, UG2002) if adapter generation fails.
✅ IntelliSense Support
Generated adapters have full XML documentation and IDE support.
🔧 How It Works
Generation Pipeline
[GenerateUnion] union type detected
↓
Source generator scans for OneOf conversions
↓
For each union, generates adapter class:
- XxxOneOfAdapter.cs
- Contains FromOneOf<T...>() method (throws)
- Contains TryFromOneOf<T...>() method (non-throwing)
↓
Compile-time validation of factory methods
↓
Full IntelliSense support
Example Generated Code
For this union:
[GenerateUnion]
public partial class Result<T, E>
{
public static Result<T, E> Ok(T value) => new OkCase(value);
public static Result<T, E> Error(E error) => new ErrorCase(error);
}
The generator creates (in Result_User_Error_OneOfAdapter.g.cs):
// Generated - do not edit
public static class Result_User_Error_OneOfAdapter
{
/// <summary>
/// Convert a OneOf value to Result<User, Error>.
/// </summary>
public static Result<User, Error> FromOneOf(
this OneOf.OneOf<User, Error> oneOf)
{
if (oneOf.IsT0)
return Result<User, Error>.Ok(oneOf.AsT0);
if (oneOf.IsT1)
return Result<User, Error>.Error(oneOf.AsT1);
throw new InvalidOperationException(
"OneOf value is in an unknown state");
}
/// <summary>
/// Try to convert a OneOf value to Result<User, Error>.
/// Returns false if conversion fails.
/// </summary>
public static bool TryFromOneOf(
this OneOf.OneOf<User, Error> oneOf,
out Result<User, Error> result)
{
try
{
result = oneOf.FromOneOf();
return true;
}
catch
{
result = default!;
return false;
}
}
}
📋 Usage Patterns
Pattern 1: Throwing Conversion (FromOneOf)
using OneOf;
var oneOf = OneOf<User, Error>.FromT0(user);
// Generated adapter - throws on invalid state
try
{
var result = oneOf.FromOneOf<Result<User, Error>, User, Error>();
// result is guaranteed valid
}
catch (InvalidOperationException ex)
{
Console.WriteLine($"Conversion failed: {ex.Message}");
}
Pattern 2: Safe Try-Pattern (TryFromOneOf)
using OneOf;
var oneOf = GetOneOfResult();
// Generated adapter - safe, non-throwing
if (oneOf.TryFromOneOf<Result<User, Error>, User, Error>(out var result))
{
// Conversion succeeded
result.Match(
ok: user => HandleSuccess(user),
error: err => HandleError(err)
);
}
else
{
// Conversion failed (OneOf in invalid state)
Console.WriteLine("Could not convert OneOf");
}
Pattern 3: Batch Conversions
using OneOf;
using System.Collections.Generic;
using System.Linq;
var oneOfResults = new List<OneOf<User, Error>>();
// Convert all safely
var results = oneOfResults
.Select(oneOf =>
{
if (oneOf.TryFromOneOf<Result<User, Error>, User, Error>(out var result))
return result;
throw new InvalidOperationException("Failed to convert");
})
.ToList();
Pattern 4: Async Integration
using OneOf;
using System.Threading.Tasks;
public async Task<Result<User, Error>> FetchUserAsync(int id)
{
var oneOfResult = await LegacyOneOfApiAsync(id);
// Generated adapter - use directly in async context
if (oneOfResult.TryFromOneOf<Result<User, Error>, User, Error>(out var result))
return result;
throw new InvalidOperationException("Failed to fetch user");
}
🔍 Compile-Time Diagnostics
UG2001: Missing Factory Method
Severity: Error
The union type doesn't have the expected factory method.
Example:
// ❌ Error: No "Ok" method
[GenerateUnion]
public partial class Result<T, E>
{
public static Result<T, E> Success(T value) => new SuccessCase(value);
public static Result<T, E> Failure(E error) => new FailureCase(error);
}
// Generator can't create adapters because "Ok" and "Error" don't exist
Fix:
// ✅ Correct
[GenerateUnion]
public partial class Result<T, E>
{
public static Result<T, E> Ok(T value) => new OkCase(value);
public static Result<T, E> Error(E error) => new ErrorCase(error);
}
UG2002: Factory Parameter Type Mismatch
Severity: Error
The factory method parameter type doesn't match the OneOf case type.
Example:
// ❌ Error: Parameter type mismatch
[GenerateUnion]
public partial class Result<T, E>
{
public static Result<T, E> Ok(string value) => ...; // Expects string, not T
public static Result<T, E> Error(E error) => ...;
}
// Cannot convert OneOf<User, Error> because Ok expects string
Fix:
// ✅ Correct
[GenerateUnion]
public partial class Result<T, E>
{
public static Result<T, E> Ok(T value) => new OkCase(value);
public static Result<T, E> Error(E error) => new ErrorCase(error);
}
⚡ Performance
Comparison: Runtime vs Compile-Time Adapters
| Operation | OneOfCompat | OneOfExtensions | OneOfSourceGen |
|---|---|---|---|
| Reflection Overhead | ~15-65 µs | ~10-35 µs | 0 ns (generated) |
| Setup Cost | None | None | Compile-time only |
| Code Size | Small | Small | Generated .g.cs |
| First-Call Cost | Reflection | Reflection | Zero |
Benchmarks
Conversion: OneOf<User, Error> → Result<User, Error>
OneOfCompat: 15-65 µs (reflection-based)
OneOfExtensions: 10-35 µs (cached reflection)
OneOfSourceGen: 10-50 ns (generated code) ← 1000x faster!
When Performance Matters
- High-frequency calls (>1000/sec): Use OneOfSourceGen
- General usage: Any approach works fine
- Hot loops: OneOfSourceGen only
- Startup-sensitive code: OneOfSourceGen has zero startup overhead
🔗 When to Use This vs Alternatives
| Feature | OneOfSourceGen | OneOfExtensions | OneOfCompat |
|---|---|---|---|
| Reflection | None (generated) | Cached reflection | Direct reflection |
| Performance | ~10-50 ns | ~10-35 µs | ~15-65 µs |
| Dependencies | UnionGenerator only | Newtonsoft.Json v13 | None |
| OneOf Versions | v2, v3 | v3+ | v2, v3 |
| Setup | Automatic | Install package | Install package |
| Best For | High-performance paths | Standard usage | Minimal deps |
📖 Best Practices
✅ DO
- Use OneOfSourceGen for hot paths and tight loops
- Leverage TryFromOneOf for safe conversions with fallback
- Let the source generator work automatically (no config needed)
- Review generated code to understand what's happening
- Use try-pattern in production code for robustness
❌ DON'T
- Mix multiple adapter approaches in same codebase (pick one)
- Assume generated code will work without compilation (compile first!)
- Ignore UG2001/UG2002 diagnostics during development
- Use throwing FromOneOf in production without try/catch (use TryFromOneOf)
- Modify generated .g.cs files (they're regenerated on build)
🚨 Troubleshooting
Generated Adapter Not Appearing
Problem: FromOneOf extension method not available
Solution:
- Rebuild project:
dotnet clean && dotnet build - Check for UG2001/UG2002 diagnostics
- Verify union type has
OkandErrorfactory methods - Check generated files in
obj/Debug/net*/generated/
Type Inference Errors
Problem: Generic type parameters can't be inferred
Solution:
// ❌ Too ambiguous
var result = oneOf.FromOneOf();
// ✅ Explicit type parameters
var result = oneOf.FromOneOf<Result<User, Error>, User, Error>();
Compilation Errors in Generated Code
Problem: Build fails with "generated code compilation error"
Solution:
- Check C# language version (11+ recommended)
- Ensure all factory methods are public and static
- Verify union type is partial
- Check for name conflicts with existing methods
- Rebuild:
dotnet clean && dotnet build
UG2001 Error
Problem: "Factory method Ok not found"
Solution:
- Verify factory method exists:
public static Result<T,E> Ok(T value) - Check method name spelling
- Ensure method is
public static - Verify return type is the union type
TryFromOneOf Always Returns False
Problem: Safe conversion method never succeeds
Solution:
- Check OneOf instance is actually constructed (use
.IsT0or.IsT1) - Verify factory methods match expected signatures
- Ensure generated code compiled (check
obj/folder) - Rebuild project
📊 Architecture
UnionGenerator.OneOfSourceGen (ISourceGenerator)
├── Detects [GenerateUnion] union types
├── Scans for OneOf usage
├── Validates factory methods
│ ├── Factory name check (Ok, Error)
│ └── Parameter type check
├── Generates adapters:
│ ├── FromOneOf<T...>() extension
│ └── TryFromOneOf<T...>() extension
└── Creates .g.cs files in obj/
Generated Adapter Classes
├── [UnionType]OneOfAdapter.g.cs
├── Extension methods on OneOf<T0, T1>
└── Zero reflection, direct implementation
🔗 Related Packages
- UnionGenerator: Core source generator (required)
- UnionGenerator.OneOfExtensions: Runtime reflection-based adapters
- UnionGenerator.OneOfCompat: Minimal-dependency adapters
- OneOf: The OneOf library this generates adapters for
📚 Integration Examples
With ASP.NET Core Controllers
[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
private readonly IUserService _service;
[HttpGet("{id}")]
public async Task<IActionResult> GetUser(int id)
{
var oneOfResult = await _service.GetUserOneOfAsync(id);
// Generated adapter - zero reflection
if (oneOfResult.TryFromOneOf<Result<User, NotFoundError>, User, NotFoundError>(
out var result))
{
return result.ToActionResult();
}
return BadRequest("Invalid state");
}
}
With LINQ Chains
var users = await _service.GetUsersAsync();
var results = users
.Select(u => u.OneOfData)
.Select(oneOf => oneOf.FromOneOf<Result<UserData, Error>, UserData, Error>())
.Where(r => r.IsSuccess)
.Select(r => r.Data)
.ToList();
📄 License
MIT (same as UnionGenerator repository)
✨ Summary
| Feature | Benefit |
|---|---|
| Zero Reflection | 1000x faster than runtime helpers |
| Automatic Generation | No configuration needed |
| Type-Safe | Compile-time validation |
| Both Patterns | FromOneOf (throwing) + TryFromOneOf (safe) |
| Hot-Path Ready | Performance-critical code paths |
Use when: You need maximum performance converting OneOf values to UnionGenerator types. For general usage, OneOfExtensions is fine. For minimal dependencies, use OneOfCompat. 🚀
Learn more about Target Frameworks and .NET Standard.
-
.NETStandard 2.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.1.0 | 115 | 1/21/2026 |
Initial release: Compile-time OneOf to UnionGenerator source generator with zero runtime overhead.