UnionUtil 0.2.0

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

UnionUtil

nuget

Source generator utilities trying to be congruent with the upcoming unions language feature in C#15. Strong focus on generating 'roughly' optimal union storage layouts within the bounds of what is allowed in the runtime. Targets net7.0 through net10.0.

Starting from 0.2.x the public API is considered stable. Breaking changes will be listed in the release notes.

Motivation

This project initially started as an experiment playing around with the .NET11 union preview feature. The currently proposed default implementation of union types will be readonly struct(object, int) to my understanding, meaning it'll box value types and generics. This is arguably the best ccompromise considering the runtime limitations, but may not always be what you want. Unions will support custom, user supplied implementations, hence the reasoning for this source generator. Mainly focusing on resolving a 'roughly' optimal memory layout by default as well as some optional parameters to finetune the generated source of the union.

Main feature set

  • Generated unions are congruent with the current compiler feature structural interface in the .NET11 preview
  • Concrete unmanaged types always share the same storage in memory in non-generic unions
  • Small buffer optimization support to avoid boxing of small unmanaged types in unions with open generics
  • Configurability of whether certain types should be stored sequentially or boxed
  • Support for mutable union types
  • Optionally tag the union with the Tagged<TEnum> attribute for labelled properties.
  • IUnionType for a non-boxing, type order agnostic common interface in generic contexts.
  • Analyzers and attributes for working with IUnionType ergonomically.
  • Compatibility extension methods broadly simulating the switch expression semantics for older projects.

Current limitations

Installation

dotnet package add UnionUtil

or add it as a project refecence in your .csproj:

  <ItemGroup>
    <ProjectReference Include="dir\to\unionutil\src\UnionUtil\UnionUtil.csproj" />
     <ProjectReference
        Include="dir\to\unionutil\src\UnionUtil.Meta\UnionUtil.Meta.csproj" 
        OutputItemType="Analyzer"
        ReferenceOutputAssembly="false"
      />
  </ItemGroup>

Usage Examples

Generating new unions:

using UnionUtil;
using static UnionUtil.UnionGeneratorOptions;
// basic union with statically know type cases
[GenerateUnion, UnionTypeArguments<int, double, DateTime>]
partial struct MyUnion;
// for trivial generic cases
[GenerateUnion(EnableReadOnly | EnableNullable)]
sealed partial class MyOtherUnion<T, U, V, W>;
// for non-trivial generic cases
[GenerateUnion(EnableUnionTypeInterface)] // will implement `IUnionType`
readonly partial struct Union<T, U, V> : IUnionTypeArguments<T, U, List<V>, double>;
// will box but store unmanaged values smaller than 15 bytes inside the inline buffer
[GenerateUnion(BoxUnconstrainedGenerics | EnableExplicitConversionsToValue), SmallBufferOptimized(15)]
partial struct MyGenericUnion<T, U, V, W, X, Y, Z>;
// tagged union
public enum Result { Ok, Err }
[GenerateUnion, Tagged<Result>]
partial struct Result<T> : IUnionTypeArguments<T, Exception>;

Using the unions:

Union<int, bool, double> myUnion = 123;
// Extension method overloads simulating switch expression syntax
// for projects targetting older .NET standards and/or language versions.
using UnionUtil.UnionTypeExtensions;
var asInt = myUnion.Switch( // does not care about generic type order
    (int i) => 1,
    (float f) => -1, // analyzer will emit warning that the union can never hold `float`
    (int i) => -1, // analyzer will emit warning that this was previously declared
    (double d) => 3,
    (bool b) => 2,
    () => 4 // default case
);
Result<int[]> result = (int[])[1, 2, 3];
// pattern match over tags
var str = result switch {
    {Tag: Result.Ok, Ok: var ok} => $"[{string.Join(", ", ok.Select(e => e.ToString()))}]",
    {Tag: Result.Err, Err: var err} => err.Message,
};

// working with unions generically
// `[HoldableTypeArgument] T` marks a type parameter to be analyzed at the point of invocation
// and emit a warning if TUnion can not realistically ever hold T. 
static TNumber UseUnion<TUnion, [HoldableTypeArgument] TNumber>(in TUnion u, TNumber v)
    where TUnion: IUnionType where TNumber : INumber<TNumber>  {

    if (!TUnion.CanHold<double>()) throw new(); // inspect whether a certain type can be held by the union
    var sboSize = TUnion.SmallBufferSize; // inspect the sbo size
    var typeCount = TUnion.TypeArgumentCount; // inspect the count of types the union can hold
    var holdsType = u.Holds<TNumber>(); // check if the union holds a specific type

    if (u.TryGetValue(out TNumber n)) return n * v; // the main magic of IUnionType which implements a generic TryGetValue<T>(out T v)
    else throw new();
}

Benchmarks

Benchmark reports can be found here. They wont always be up-to-date, nor always be a good representative for real-world code. Mainly used to visualize how well internal implementations compare to other ones, so i can determine which implementations are underperforming.

Product Compatible and additional computed target framework versions.
.NET net7.0 is compatible.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  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 is compatible.  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 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.
  • net7.0

    • No dependencies.
  • net8.0

    • No dependencies.
  • net9.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
0.2.0 37 5/3/2026
0.1.1 81 4/29/2026
0.1.0 88 4/26/2026
0.0.4 85 4/20/2026
0.0.3 97 4/17/2026
0.0.2 84 4/17/2026
0.0.1 101 4/13/2026
0.0.0 86 4/13/2026