Compile-time discriminated unions for C# with zero runtime overhead.
Eliminate boilerplate, enable type-safe error handling, and leverage exhaustive pattern matching—all without exceptions or nullable chains.
dotnet add package UnionGeneratorusing 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);
}var result = Result<int, string>.Ok(42);
// Pattern matching
var message = result switch
{
{ IsSuccess: true, Data: var data } => $"Success: {data}",
{ IsSuccess: false, Error: var error } => $"Error: {error}",
};
// Or use Match method
string message = result.Match(
ok: data => $"Success: {data}",
error: err => $"Error: {err}"
);| Feature | Description |
|---|---|
| 🔧 Source Generation | Zero runtime reflection, pure compile-time generated code |
| 🎯 Type Safety | Exhaustive pattern matching with compiler-enforced case coverage |
| 🚀 Zero Overhead | No allocations, no boxing, minimal memory footprint |
| 🔗 ASP.NET Core Integration | Automatic ProblemDetails (RFC 7807) conversion |
| 💾 EF Core Support | JSON value converters for database persistence |
| ✅ FluentValidation Integration | Seamless validation error mapping |
| 🔄 OneOf Compatibility | Drop-in replacement for OneOf library |
| 📊 Roslyn Analyzers | Compile-time diagnostics and code fixes |
| Package | Description | NuGet |
|---|---|---|
| UnionGenerator | Core source generator and attributes |
| Package | Description | NuGet |
|---|---|---|
| UnionGenerator.Analyzers | Roslyn analyzers for union usage | |
| UnionGenerator.Analyzers.CodeFixes | Code fix providers |
- Core Library Documentation - Complete guide to union generation, patterns, and best practices
- Examples Overview - Quick reference for all examples
| Integration | Documentation | Example Project |
|---|---|---|
| ASP.NET Core | View Docs | aspnetcore-example |
| Entity Framework Core | View Docs | entityframework-example |
| FluentValidation | View Docs | fluentvalidation-example |
| JSON Serialization | - | json-example |
| OneOf Migration | View Docs | oneof-example |
All examples are production-ready and demonstrate real-world patterns:
Complete REST API with automatic ProblemDetails conversion, Swagger integration, and controller/minimal API examples.
cd examples/aspnetcore-example
dotnet run
# Navigate to https://localhost:5001/swaggerDatabase persistence with JSON value converters, CRUD operations, and query patterns.
cd examples/entityframework-example
dotnet runDeclarative validation with automatic error conversion to ProblemDetails format.
cd examples/fluentvalidation-example
dotnet runSystem.Text.Json integration with roundtrip serialization of union types.
cd examples/json-example
dotnet runDrop-in replacement demonstration for migrating from OneOf library.
cd examples/oneof-example
dotnet runUnionGenerator/
├── src/
│ ├── UnionGenerator/ # Core source generator
│ ├── UnionGenerator.Analyzers/ # Roslyn analyzers
│ ├── UnionGenerator.Analyzers.CodeFixes/# Code fix providers
│ ├── UnionGenerator.AspNetCore/ # ASP.NET Core integration
│ ├── UnionGenerator.EntityFrameworkCore/# EF Core integration
│ ├── UnionGenerator.FluentValidation/ # FluentValidation integration
│ ├── UnionGenerator.OneOfCompat/ # OneOf compatibility
│ └── UnionGenerator.OneOfExtensions/ # OneOf extensions
├── examples/ # Production-ready examples
│ ├── aspnetcore-example/
│ ├── entityframework-example/
│ ├── fluentvalidation-example/
│ ├── json-example/
│ └── oneof-example/
├── tests/ # Comprehensive test suite
│ ├── UnionGenerator.Tests/
│ ├── UnionGenerator.AspNetCore.Tests/
│ ├── UnionGenerator.EntityFrameworkCore.Tests/
│ └── UnionGenerator.FluentValidation.Tests/
└── docs/ # Additional documentation
[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<User, string> GetUser(int id)
{
if (id <= 0)
{
return Result<User, string>.Error("Invalid ID");
}
var user = _userService.FindById(id);
return user != null
? Result<User, string>.Ok(user)
: Result<User, string>.Error("User not found");
}[GenerateUnion]
public partial class ApiResponse<T>
{
public static ApiResponse<T> Success(T data) => new SuccessCase(data);
public static ApiResponse<T> NotFound(string message) => new NotFoundCase(message);
public static ApiResponse<T> Unauthorized() => new UnauthorizedCase();
public static ApiResponse<T> ValidationError(Dictionary<string, string[]> errors)
=> new ValidationErrorCase(errors);
}[GenerateUnion]
public partial class OrderState
{
public static OrderState Pending() => new PendingCase();
public static OrderState Processing(string workerId) => new ProcessingCase(workerId);
public static OrderState Completed(DateTime completedAt) => new CompletedCase(completedAt);
public static OrderState Cancelled(string reason) => new CancelledCase(reason);
}[GenerateUnion]
public partial class Option<T>
{
public static Option<T> Some(T value) => new SomeCase(value);
public static Option<T> None() => new NoneCase();
}Traditional C# approaches for handling multiple return types are problematic:
| Approach | Issues |
|---|---|
| Nullable types | Lose type information, awkward null-checking chains |
| Exceptions | Performance overhead, lose error context, not for control flow |
| Out parameters | Unidiomatic, not composable |
| Custom base classes | Boilerplate-heavy, no exhaustiveness checking |
Discriminated unions provide:
- ✅ Type Safety - Compiler-enforced exhaustive matching
- ✅ Performance - Zero runtime overhead, no exceptions
- ✅ Clarity - Intent is explicit, errors are values
- ✅ Composability - Natural functional patterns
- ✅ Maintainability - Less boilerplate, more focus on logic
- .NET 8.0 SDK or later
- JetBrains Rider (recommended) or Visual Studio 2022+
# Clone the repository
git clone https://github.com/yourusername/UnionGenerator.git
cd UnionGenerator
# Restore dependencies
dotnet restore
# Build all projects
dotnet build
# Run tests
dotnet test
# Pack NuGet packages
dotnet pack -c Release -o nupkgsEach example project can be run independently:
# ASP.NET Core example
cd examples/aspnetcore-example
dotnet run
# Entity Framework Core example
cd examples/entityframework-example
dotnet run
# All examples follow the same pattern# Run all tests
dotnet test
# Run specific test project
dotnet test tests/UnionGenerator.Tests/
# Run with code coverage
dotnet test --collect:"XPlat Code Coverage"This project uses GitHub Actions for automated NuGet package publishing.
# Create and push a version tag
git tag -a v0.2.0 -m "Release version 0.2.0"
git push origin v0.2.0
# GitHub Actions will automatically:
# ✅ Build and test
# ✅ Pack NuGet packages
# ✅ Publish to NuGet.org
# ✅ Create GitHub Release- NuGet API Key: Add
NUGET_API_KEYsecret to GitHub repository settings - Full Instructions: See Publishing Guide
- Automatic (Recommended): Push a version tag (e.g.,
v0.1.0) - Manual: Use GitHub Actions workflow dispatch UI
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
- Follow the existing code style (Rider/ReSharper conventions)
- Add tests for new features
- Update documentation as needed
- Keep commits focused and atomic
This project is licensed under the MIT License - see the LICENSE file for details.
- Inspired by F#'s discriminated unions and Rust's enums
- Built with Roslyn source generators
- Community feedback and contributions
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- Documentation: Core Docs | Examples
Made with ❤️ for the C# community