Toarnbeike.Unions 1.3.0

dotnet add package Toarnbeike.Unions --version 1.3.0
                    
NuGet\Install-Package Toarnbeike.Unions -Version 1.3.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="Toarnbeike.Unions" Version="1.3.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Toarnbeike.Unions" Version="1.3.0" />
                    
Directory.Packages.props
<PackageReference Include="Toarnbeike.Unions" />
                    
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 Toarnbeike.Unions --version 1.3.0
                    
#r "nuget: Toarnbeike.Unions, 1.3.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 Toarnbeike.Unions@1.3.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=Toarnbeike.Unions&version=1.3.0
                    
Install as a Cake Addin
#tool nuget:?package=Toarnbeike.Unions&version=1.3.0
                    
Install as a Cake Tool

CI .NET 10 License: MIT

Toarnbeike.Unions

This package provides immutable, allocation-free union types for C#, inspired by functional programming and discriminated unions, while remaining idiomatic to the .NET ecosystem.

A Union<T1, ..., Tn> represents a value that can be exactly one of several possible types at runtime, without relying on inheritance, nulls, or exceptions for control flow. This is particularly useful for modeling domain states and workflows where a value can be in one of a fixed set of alternatives.

Features

  • Generated Union types with 2 to 8 generic type parameters (expandable via included generators)
  • Explicit construction and state inspection
  • Rich functional extensions (Map, Bind, Match, Switch, Tap)
  • Async support for all extensions
  • Collection extensions for partitioning
  • Test extensions for fluent assertions
  • Unit tested with high code coverage

Contents

  1. Quick start
  2. Core concepts
  3. Extensions
  4. Collections
  5. Test extensions
  6. Design principles
  7. Comparison: Why Unions?
  8. When not to use Unions?
  9. Conclusion

Quick start

This example demonstrates the most common workflow when using unions: construction, transformation, and consumption.

using Toarnbeike.Unions.Generic;
using Toarnbeike.Unions.Generic.Extensions;

Union<int, string> value = Union<int, string>.FromT1(10);

// Transform the value
var transformed = value
    .Map(
        t1 => t1 + 1,
        t2 => t2.Length
    );

// Consume the union
var result = transformed.Match(
    t1 => $"Int: {t1}",
    t2 => $"String length: {t2}"
);

Console.WriteLine(result); // Output: Int: 11

Key properties of this workflow:

  • The union is always in exactly one state
  • All transformations are explicit
  • All states must be handled exhaustively
  • No nulls, casts, or exceptions are required

Core concepts

What is a Union?

A union represents one of several possible states, each carrying a value of a different type.

Union<int, string> value = Union<int, string>.FromT1(42);
// or
Union<int, string>.FromT2("hello");

At any point in time, a union is in exactly one state.

Construction

Unions are constructed via explicit static factory methods:

var u1 = Union<int, string>.FromT1(10);
var u2 = Union<int, string>.FromT2("text");

This ensures:

  • No ambiguous states
  • No implicit conversions
  • No null-based discrimination

State Inspection

Each union exposes state checks:

if (union.IsT1) { ... }
if (union.IsT2) { ... }

Safe extraction using TryGet is available; TryGet never throws and does not allocate.

if (union.TryGetT1(out T1 value)) { ... }

Extensions

The Toarnbeike.Unions.Extensions namespace contains rich extension methods for all Union<T1,...,Tn>.

Method Returns Description
Match(...) TResult Consume the union by handling all possible states.
Switch(...) void Handle a side effect for each possible state.
Map(...) Union<...> Transform one or all values within the Union.
Bind(...) Union<...> State-dependent transitions to another Union<>.
Tap(...) Union<...> Side-effects for a specific state (fluent).

All extensions include async overloads and Task<Union<...>> variants.

For information per method, see the Extensions README.


Collections

The Toarnbeike.Unions.Collections namespace contains extension methods to work with IEnumerable<Union<...>>.

This adds the possibility to split a collection of unions in separate collections of the different types present:

Method Returns Description
SelectTi(...) IEnumerable<Ti> Select all instances of Ti in the IEnumerable<Union>.
Partition(...) Tuple<IReadOnlyList<Ti..Tn>> Tuple with an IReadOnlyList of instances of each type.

SelectTi

Select Ti extracts all values of type Ti from a collection of unions.

private IEnumerable<Union<U1,U2>> CreateMixedCollection()
{
    yield return Union<U1,U2>.FromT1(new U1(1));
    yield return Union<U1,U2>.FromT1(new U1(2));
    yield return Union<U1,U2>.FromT2(new U2(2));
    yield return Union<U1,U2>.FromT2(new U2(3));
}

var t1Values = CreateMixedCollection().SelectT1().ToList();

t1Values.Count.ShouldBe(2);
t1Values[0].Value.ShouldBe(1);
t1Values[1].Value.ShouldBe(2);

Partition

Partition splits a collection of unions into separate collections for each type. This is useful when you need to process each union case in bulk.

var source = CreateMixedCollection();

var (t1Values, t2Values) = source.Partition();

t1Values.Count.ShouldBe(2);
t2Values.Count.ShouldBe(2);

Test extensions

The Toarnbeike.Unions.TestExtensions namespace contains extensions for asserting on Unions in tests (framework-agnostic).

Per arity the following methods are available:

Method Returns Description
ShouldBeTi() Ti Asserts the union is in state Ti and returns value.
ShouldBeTi(Ti) Ti Asserts the union is in state Ti with expected value.

Usage

var t1 = union.ShouldBeT1();
var actual = union.ShouldBeT1(expectedValue);

Design principles and best practices

This library intentionally favors:

  • Explicit over implicit
  • Composition over convenience
  • Minimal primitives over combinator explosion
  • Functional correctness over syntactic shortcuts

Notably out of scope:

  • Implicit conversions (types might not be unique within a union)
  • Null-based semantics (use Options)

Comparison: Why Unions?

Alternative Typical use case How unions differ
Inheritance Behavioral polymorphism Unions model closed, explicit state alternatives.
Enums State without data Unions associate each state with its own value.
Nulls Optional references Unions encode alternatives explicitly.
Exceptions Unexpected failures Unions model expected outcomes.
Option / Result Presence / success vs failure Unions generalize this to multiple meaningful states.

For a more detailed comparison, see the Comparison document.


When not to use Unions?

Scenario Prefer instead Why
Simple success / failure Result<T> Binary outcomes are clearer with a dedicated abstraction.
Optional presence only Option<T> Avoid encoding absence as an extra union case.
Open-ended or extensible hierarchies Inheritance / interfaces Unions are intentionally closed.
Cross-cutting behavioral polymorphism Interfaces Unions model data states, not shared behavior.
Performance-critical hot paths (micro) Specialized structs Pattern matching may be unnecessary overhead.

Unions are a powerful modeling tool, but they are intentionally not universal.


Conclusion

Union<T1, ..., Tn> enables:

  • Strongly typed alternatives
  • Exhaustive handling
  • Functional transformations
  • Explicit side effects
  • Predictable async composition

All while remaining:

  • Immutable
  • Allocation-efficient
  • Generator-friendly
  • Testable

This makes Unions suitable for domain modeling, state machines, and functional workflows in modern C#.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  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.
  • net10.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
1.3.0 104 2/1/2026
1.2.0 110 12/30/2025
1.1.1 245 12/19/2025
1.1.0 252 12/19/2025
1.0.0 280 12/17/2025