UnionGenerator.OneOfCompat 0.1.0

dotnet add package UnionGenerator.OneOfCompat --version 0.1.0
                    
NuGet\Install-Package UnionGenerator.OneOfCompat -Version 0.1.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="UnionGenerator.OneOfCompat" Version="0.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="UnionGenerator.OneOfCompat" Version="0.1.0" />
                    
Directory.Packages.props
<PackageReference Include="UnionGenerator.OneOfCompat" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add UnionGenerator.OneOfCompat --version 0.1.0
                    
#r "nuget: UnionGenerator.OneOfCompat, 0.1.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package UnionGenerator.OneOfCompat@0.1.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=UnionGenerator.OneOfCompat&version=0.1.0
                    
Install as a Cake Addin
#tool nuget:?package=UnionGenerator.OneOfCompat&version=0.1.0
                    
Install as a Cake Tool

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:

  1. Takes a value of type T0 or T1
  2. Uses reflection to find the factory method on the generated union
  3. Invokes Ok(value) or Error(value) statically
  4. 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:

  1. Create adapter service wrapping legacy OneOf service
  2. Update new code to use adapter (returns UnionGenerator types)
  3. Gradually rewrite legacy service to use UnionGenerator
  4. 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

  1. Cache factory methods if converting many times (reflection is expensive)
  2. 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);
    
  3. Batch conversions to amortize reflection cost
  4. 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:

  1. Verify union type has static factory method: public static Result Ok(T value)
  2. Ensure method is public and static
  3. Verify method name matches ("Ok" or "Error")
  4. 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:

  1. Ensure generated code actually compiled (check obj/Debug/ for .g.cs)
  2. Try dotnet clean && dotnet build
  3. Verify union type is partial so code generation works
  4. 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

  • 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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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.