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 | Downloads |
|---|---|---|---|
| UnionGenerator | Core source generator and attributes |
| Package | Description | NuGet | Downloads |
|---|---|---|---|
| UnionGenerator.Analyzers | Roslyn analyzers for union usage | ||
| UnionGenerator.Analyzers.CodeFixes | Code fix providers |
📖 Complete Documentation - Comprehensive guides, API reference, and examples
- Getting Started - 5-minute quick start guide
- Core Package Guide - Union generation and pattern matching
- Integration Packages - ASP.NET Core, EF Core, FluentValidation
- API Reference - Complete API documentation
- Contributing Guide - How to contribute
- 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 fully automated NuGet package publishing.
When you merge code to main branch with an updated version number, packages are automatically published to NuGet.org:
# 1. Update version in .csproj files
# Edit src/UnionGenerator/UnionGenerator/UnionGenerator.csproj:
# <Version>0.2.0</Version> (change from 0.1.0)
# 2. Commit and push to main
git add .
git commit -m "Release v0.2.0: Added new features"
git push origin main
# GitHub Actions will automatically:
# ✅ Detect version change (0.1.0 → 0.2.0)
# ✅ Build and test
# ✅ Pack NuGet packages
# ✅ Publish to NuGet.org
# ✅ Create Git tag (v0.2.0)
# ✅ Create GitHub ReleaseHow it works:
- Workflow compares .csproj version with previous commit
- If version changed → automatic publish
- If version unchanged → skip publish
You can also publish by creating a version tag:
git tag -a v0.2.0 -m "Release version 0.2.0"
git push origin v0.2.0-
NuGet API Key: Add
NUGET_API_KEYsecret to GitHub repository settings- Go to: Repository Settings → Secrets and variables → Actions
- Create new secret:
NUGET_API_KEY - Value: Your NuGet.org API key
-
GitHub Token: Automatically provided by GitHub Actions (no setup needed)
| Workflow | Trigger | Purpose |
|---|---|---|
| publish-on-version-change.yml | Push to main (version changed) |
Automatic CD pipeline |
| publish-nuget.yml | Version tag push (v*.*.*) |
Manual release via tag |
| publish-manual.yml | Workflow dispatch | Emergency manual publish |
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