UnionGenerator.OneOfCompat
0.1.0
dotnet add package UnionGenerator.OneOfCompat --version 0.1.0
NuGet\Install-Package UnionGenerator.OneOfCompat -Version 0.1.0
<PackageReference Include="UnionGenerator.OneOfCompat" Version="0.1.0" />
<PackageVersion Include="UnionGenerator.OneOfCompat" Version="0.1.0" />
<PackageReference Include="UnionGenerator.OneOfCompat" />
paket add UnionGenerator.OneOfCompat --version 0.1.0
#r "nuget: UnionGenerator.OneOfCompat, 0.1.0"
#:package UnionGenerator.OneOfCompat@0.1.0
#addin nuget:?package=UnionGenerator.OneOfCompat&version=0.1.0
#tool nuget:?package=UnionGenerator.OneOfCompat&version=0.1.0
UnionGenerator.OneOfCompat
Runtime helpers to convert OneOf<T0,T1> values into UnionGenerator-created discriminated union types. Provides lightweight, reflection-based interoperability with the OneOf library.
๐ Quick Start (2 minutes)
1. Install
dotnet add package UnionGenerator.OneOfCompat
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
using OneOf;
using UnionGenerator.OneOfCompat;
OneOf<User, string> oneOfResult = GetOneOfResult();
// Convert to generated union using factory methods
var result = OneOfCompat.FromT0<Result<User, string>, User, string>(oneOfResult.AsT0);
// Or use the helper directly
var generatedResult = Result<User, string>.Ok(oneOfResult.AsT0);
That's it! Your OneOf values are now UnionGenerator unions.
๐ Features
โ Reflection-Based Conversion
Uses reflection to invoke generated factory methods dynamically.
โ Zero External Dependencies (Core Only)
Only requires System.* namespaces. No additional NuGet packages.
โ Supports OneOf v2 & v3
Works with both OneOf v2.x and v3.x library versions.
โ Simple API
Two helper methods: FromT0() and FromT1() for binary unions.
โ Safe Type Checking
Validates types at runtime before conversion.
๐ง Core Components
OneOfCompat Class
namespace UnionGenerator.OneOfCompat;
public static class OneOfCompat
{
/// <summary>
/// Create a generated union from the first case (T0).
/// </summary>
public static TGenerated FromT0<TGenerated, TSuccess, TError>(TSuccess value)
where TGenerated : class;
/// <summary>
/// Create a generated union from the second case (T1).
/// </summary>
public static TGenerated FromT1<TGenerated, TSuccess, TError>(TError value)
where TGenerated : class;
}
How It Works:
- Takes a value of type T0 or T1
- Uses reflection to find the factory method on the generated union
- Invokes
Ok(value)orError(value)statically - Returns the generated union instance
๐ Usage Patterns
Pattern 1: Converting OneOf to Result Union
using OneOf;
using UnionGenerator.OneOfCompat;
public class User { public int Id { get; set; } }
// Your OneOf-based function
public OneOf<User, string> GetUserOneOf(int id)
{
var user = _service.FindById(id);
return user != null
? user
: "User not found";
}
// Generated 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);
}
// Convert OneOf to Result
public Result<User, string> GetUser(int id)
{
var oneOfResult = GetUserOneOf(id);
if (oneOfResult.IsT0)
{
return OneOfCompat.FromT0<Result<User, string>, User, string>(oneOfResult.AsT0);
}
else
{
return OneOfCompat.FromT1<Result<User, string>, User, string>(oneOfResult.AsT1);
}
}
Pattern 2: Legacy to Modern Migration
If you have legacy OneOf code and want to migrate to UnionGenerator:
Before (OneOf):
using OneOf;
public OneOf<Data, Error> Process(Input input)
{
try
{
var data = DoWork(input);
return data;
}
catch (Exception ex)
{
return new Error { Message = ex.Message };
}
}
// Callers
var result = Process(input);
result.Switch(
data => HandleSuccess(data),
error => HandleError(error)
);
After (UnionGenerator):
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);
}
public Result<Data, Error> Process(Input input)
{
try
{
var data = DoWork(input);
return Result<Data, Error>.Ok(data);
}
catch (Exception ex)
{
return Result<Data, Error>.Error(new Error { Message = ex.Message });
}
}
// Callers (modern pattern matching)
var result = Process(input);
result.Match(
ok: data => HandleSuccess(data),
error: error => HandleError(error)
);
Pattern 3: Gradual Migration with Adapter Functions
When: Large codebase with many OneOf-dependent functions; want to migrate incrementally without rewriting everything at once.
Strategy: Create adapter functions that wrap OneOf results and convert to UnionGenerator types at boundaries.
// Step 1: Keep OneOf internally in legacy service
public class LegacyUserService
{
public OneOf<User, string> GetUserLegacy(int id)
{
// Old OneOf-based implementation
if (id <= 0) return "Invalid ID";
return new User { Id = id, Name = "John" };
}
}
// Step 2: Create adapter layer that bridges OneOf โ UnionGenerator
[GenerateUnion]
public partial class UserResult
{
public static UserResult Success(User user) => new SuccessCase(user);
public static UserResult NotFound(string message) => new NotFoundCase(message);
}
public class UserServiceAdapter
{
private readonly LegacyUserService _legacy;
public UserServiceAdapter(LegacyUserService legacy)
{
_legacy = legacy;
}
// Adapter method: converts OneOf โ UnionGenerator type
public UserResult GetUser(int id)
{
var legacyResult = _legacy.GetUserLegacy(id);
return legacyResult.Match(
user => UserResult.Success(user),
error => UserResult.NotFound(error)
);
}
}
// Step 3: Use adapter in new code
var adapter = new UserServiceAdapter(legacyService);
var result = adapter.GetUser(1); // Returns UserResult, not OneOf
result.Match(
success: user => Console.WriteLine($"User: {user.Name}"),
notFound: msg => Console.WriteLine($"Error: {msg}")
);
// Step 4: Over time, rewrite legacy service to use UnionGenerator directly
// Delete LegacyUserService once all callers migrated to adapter
Benefits:
- โ Incrementally migrate without big-bang rewrite
- โ New code uses UnionGenerator immediately
- โ Legacy code untouched until rewritten
- โ Clear boundary between old and new patterns
- โ Easy to track migration progress (adapter methods are the boundary)
Migration Checklist:
- Create adapter service wrapping legacy OneOf service
- Update new code to use adapter (returns UnionGenerator types)
- Gradually rewrite legacy service to use UnionGenerator
- Delete adapter once legacy service fully rewritten
โก Performance
Reflection Overhead
| Operation | Time | Notes |
|---|---|---|
| Factory method lookup (first) | ~10-50 ยตs | Reflected, cached internally |
| Factory method invocation | ~5-15 ยตs | Delegate call |
| Total conversion | ~15-65 ยตs | Per conversion |
Optimization Tips
- Cache factory methods if converting many times (reflection is expensive)
- Use direct assignment where possible instead of helpers:
// Better: Direct factory call (no reflection) var result = Result<User, string>.Ok(user); // Worse: Uses reflection var result = OneOfCompat.FromT0<Result<User, string>, User, string>(user); - Batch conversions to amortize reflection cost
- Consider OneOfSourceGen for compile-time code generation (zero reflection)
๐ When to Use This vs Alternatives
OneOfCompat vs OneOfExtensions
| Feature | OneOfCompat | OneOfExtensions |
|---|---|---|
| Dependencies | None (core only) | Newtonsoft.Json v13 |
| OneOf Version | v2, v3 | v3+ |
| API Style | Static helper methods | Extension methods |
| Performance | ~15-65 ยตs | ~5-15 ยตs (less reflection) |
| Best For | Lightweight, no-dependency projects | Standard OneOf v3 projects |
OneOfCompat vs OneOfSourceGen
| Feature | OneOfCompat | OneOfSourceGen |
|---|---|---|
| When Code Runs | Runtime (reflection) | Compile-time (generated) |
| Reflection Overhead | ~15-65 ยตs | None (zero) |
| Setup | Easy (just install) | Requires [GenerateUnion] |
| Best For | Quick integration, legacy code | High-performance code paths |
| Code Size | Small | Generates .g.cs files |
๐ Best Practices
โ DO
- Use direct factory calls when you know the type at compile time
- Use helpers only for dynamic/unknown type scenarios
- Cache factory methods if converting many times per second
- Document which code uses OneOf vs UnionGenerator
- Plan migration from OneOf to UnionGenerator gradually
โ DON'T
- Use reflection helpers in hot loops (performance hazard)
- Mix OneOf and UnionGenerator randomly (pick one per codebase)
- Forget that factory methods must match expected names (Ok, Error)
- Assume conversion is zero-cost (it uses reflection)
- Use this in high-frequency trading or extreme performance scenarios (use OneOfSourceGen instead)
๐จ Troubleshooting
Factory Method Not Found
Problem:
InvalidOperationException: Factory method 'Ok' not found on type 'Result'
Solution:
- Verify union type has static factory method:
public static Result Ok(T value) - Ensure method is
publicandstatic - Verify method name matches ("Ok" or "Error")
- Check namespace matches
Wrong Type Conversion
Problem: Creating Result<A, B> but passing Result<C, D> types
Solution:
// โ Wrong - type mismatch
OneOfCompat.FromT0<Result<User, string>, int, string>(42); // User != int
// โ
Correct
OneOfCompat.FromT0<Result<int, string>, int, string>(42);
Reflection Not Finding Method
Problem: Factory method exists but reflection can't find it
Solution:
- Ensure generated code actually compiled (check
obj/Debug/for.g.cs) - Try
dotnet clean && dotnet build - Verify union type is
partialso code generation works - Check for naming conflicts or overloads
๐ Architecture
OneOfCompat (Static Helpers)
โโโ FromT0<TGenerated, T0, T1>(T0 value)
โ โโโ Uses Reflection to call TGenerated.Ok(value)
โโโ FromT1<TGenerated, T0, T1>(T1 value)
โโโ Uses Reflection to call TGenerated.Error(value)
Generated Union Type
โโโ Static factories: Ok(), Error()
โโโ Runtime properties: IsSuccess, Data, Error
๐ Related Packages
- UnionGenerator: Core source generator
- UnionGenerator.OneOfExtensions: Alternative with Newtonsoft.Json helpers
- UnionGenerator.OneOfSourceGen: Compile-time adapters (zero reflection)
- OneOf: The OneOf library this provides compatibility with
๐ License
MIT (same as UnionGenerator repository)
โจ Summary
| Feature | Benefit |
|---|---|
| Zero Dependencies | Lightweight integration |
| Simple API | Two methods (FromT0, FromT1) |
| Reflection-Based | Dynamic type handling |
| Fast Setup | Just install, no config |
| Backwards Compatible | Works with OneOf v2 & v3 |
Use when: You need to convert OneOf values to UnionGenerator types with minimal dependencies. For high-performance paths, use OneOfSourceGen instead. ๐
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net8.0
- UnionGenerator (>= 0.1.0)
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 | 95 | 1/21/2026 |
Initial release: OneOf library compatibility layer for gradual migration to UnionGenerator.