Skip to content

coplt/Coplt.Union

Repository files navigation

Coplt.Union

.NET MIT

  • Sera.Union
    Nuget openupm
  • Sera.Union.Utilities
    Nuget

Generate Tagged Union using source generator

  • Source distribution, no runtime dependencies
  • All unmanaged types will overlap
  • All classes will overlap
  • Other types are sequential
  • Support generics, but generics cannot overlap

Example

[Union]
public readonly partial struct Union1
{
    // This template is not used at runtime, its only purpose is to provide type symbols to the roslyn analyzer
    [UnionTemplate]
    private interface Template
    {
        int A();
        string B();
        bool C();
        (int a, int b) D();
        void E();               // Tag only variant
        List<int>? F();
        (int a, string b) G();
        // Record mode, in this mode, multiple fields are stored separately.
        void H(int a, int b, string c, HashSet<int> d, (int a, string b) e);
    }
}

Will generate (pseudocode):

public readonly partial struct Union1
{
    private readonly __impl_ _impl;
    
    // If the first item is a Tag only, it starts at 0, otherwise it starts at 1
    // Use [UnionTag(value)] to explicitly mark enum values
    public enum Tags : byte { A = 1, B, C, D, E, F, G, H }
    
    private struct __impl_
    {
        public object? _c0;                 // All classes will overlap
        public object? _c1;
        public __unmanaged_ _u;             // All unmanaged types will overlap
        public (int a, string b) _f0_0;     // Mixed types cannot overlap
        public readonly Tags _tag;
        
        [StructLayout(LayoutKind.Explicit)] internal struct __unmanaged_
        {
            [FieldOffset(0)] public int  _0;
            [FieldOffset(0)] public bool _1;
            [FieldOffset(0)] public (int a, int b) _2;
            [FieldOffset(0)] public __record_7_unmanaged_ _3;
        }
        
        // The fields of the record mode variant are stored separately, with the unmanaged part having a separate structure
        internal struct __record_7_unmanaged_
        {
            public int _0;
            public int _1;
        }
    }
    
    public static Union1 MakeA(int value) { ... }
    public static Union1 MakeB(string value) { ... }
    public static Union1 MakeC(bool value) { ... }
    public static Union1 MakeD((int a, int b) value) { ... }
    public static Union1 MakeE() { ... }
    public static Union1 MakeF(List<int>? value) { ... }
    public static Union1 MakeG((int a, string b) value) { ... }
    public static Union1 MakeH(int a, int b, string c, HashSet<int> d, (int a, string b) e) { ... }
    
    public readonly Tags Tag { get; }
    public readonly bool IsA { get; }
    public readonly bool IsB { get; }
    public readonly bool IsC { get; }
    public readonly bool IsD { get; }
    public readonly bool IsE { get; }
    public readonly bool IsF { get; }
    public readonly bool IsG { get; }
    public readonly bool IsH { get; }
    
    // ref readonly if sturct is readonly, otherwise ref only
    // If the current tag does not match, a null reference will be returned
    public ref readonly int A { get; }
    public ref readonly string B { get; }
    public ref readonly bool C { get; }
    public ref readonly (int a, int b) D { get; }
    // E is a Tag only so there is no value getter
    public ref readonly List<int>? F { get; }
    public ref readonly (int a, string b) G { get; }
    // Record mode will get a magic view struct which has some getters to calculate the field references
    public ref readonly VariantHView H => ref Unsafe.As<__impl_, VariantHView>(ref _impl);
    
    ... Eq Cmp ToString
    
    // The magic struct, that can use the this reference to compute the actual field reference by reinterpreting __impl_ as this struct
    public struct VariantHView
    {
        private readonly __impl_ _impl;
        
        public ref readonly int a { get; }
        public ref readonly int b { get; }
        public ref readonly string c { get; }
        public ref readonly HashSet<int> d { get; }
        public ref readonly (int a, string b) e { get; }
        
        ...
    }
}

Complete generate output:

Union1.union.g.cs
// <auto-generated/>

#nullable disable warnings
#nullable enable annotations

using Coplt.Union;

public readonly partial struct Union1
    : global::Coplt.Union.ITaggedUnion
    , global::System.IEquatable<Union1>
    , global::System.IComparable<Union1>
    #if NET7_0_OR_GREATER
    , global::System.Numerics.IEqualityOperators<Union1, Union1, bool>
    , global::System.Numerics.IComparisonOperators<Union1, Union1, bool>
    #endif
{
    private readonly __impl_ _impl;
    private Union1(__impl_ _impl) { this._impl = _impl; }

    public readonly Tags Tag
    {
        [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
        get => this._impl._tag;
    }

    public enum Tags : byte
    {
        A = 1,
        B,
        C,
        D,
        E,
        F,
        G,
        H,
    }

    [global::System.Runtime.CompilerServices.CompilerGenerated]
    [global::System.Runtime.InteropServices.StructLayout(global::System.Runtime.InteropServices.LayoutKind.Auto)]
    internal struct __impl_
    {
        public object? _c0;
        public object? _c1;
        public __unmanaged_ _u;
        public (int a, string b) _f0_0;
        public readonly Tags _tag;

        [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
        public __impl_(Tags _tag)
        {
            this._c0 = null;
            this._c1 = null;
            global::System.Runtime.CompilerServices.Unsafe.SkipInit(out this._u);
            this._f0_0 = default!;
            this._tag = _tag;
        }

        [global::System.Runtime.CompilerServices.CompilerGenerated]
        [global::System.Runtime.InteropServices.StructLayout(global::System.Runtime.InteropServices.LayoutKind.Explicit)]
        internal struct __unmanaged_
        {
            [global::System.Runtime.InteropServices.FieldOffset(0)]
            public int _0;
            [global::System.Runtime.InteropServices.FieldOffset(0)]
            public bool _1;
            [global::System.Runtime.InteropServices.FieldOffset(0)]
            public (int a, int b) _2;
            [global::System.Runtime.InteropServices.FieldOffset(0)]
            public __record_7_unmanaged_ _3;
        }

        [global::System.Runtime.CompilerServices.CompilerGenerated]
        [global::System.Runtime.InteropServices.StructLayout(global::System.Runtime.InteropServices.LayoutKind.Auto)]
        internal struct __record_7_unmanaged_
        {
            public int _0;
            public int _1;
        }
    }

    public partial record struct VariantH(int a, int b, string c, global::System.Collections.Generic.HashSet<int> d, (int a, string b) e);

    public readonly struct VariantHView : 
        global::System.IEquatable<VariantHView>
        , global::System.IComparable<VariantHView>
        #if NET7_0_OR_GREATER
        , global::System.Numerics.IEqualityOperators<VariantHView, VariantHView, bool>
        , global::System.Numerics.IComparisonOperators<VariantHView, VariantHView, bool>
        #endif
    {
        private readonly __impl_ _impl;

        [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
        public VariantHView()
        {
            _impl = new __impl_(Tags.H);
        }

        [global::System.Diagnostics.CodeAnalysis.UnscopedRef]
        public ref readonly int a
        {
            [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
            get => ref global::System.Runtime.CompilerServices.Unsafe.AsRef<Union1.__impl_>(in this._impl)._u._3._0;
        }
        [global::System.Diagnostics.CodeAnalysis.UnscopedRef]
        public ref readonly int b
        {
            [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
            get => ref global::System.Runtime.CompilerServices.Unsafe.AsRef<Union1.__impl_>(in this._impl)._u._3._1;
        }
        [global::System.Diagnostics.CodeAnalysis.UnscopedRef]
        public ref readonly string c
        {
            [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
            get => ref global::System.Runtime.CompilerServices.Unsafe.As<object?, string>(ref global::System.Runtime.CompilerServices.Unsafe.AsRef<Union1.__impl_>(in this._impl)._c0);
        }
        [global::System.Diagnostics.CodeAnalysis.UnscopedRef]
        public ref readonly global::System.Collections.Generic.HashSet<int> d
        {
            [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
            get => ref global::System.Runtime.CompilerServices.Unsafe.As<object?, global::System.Collections.Generic.HashSet<int>>(ref global::System.Runtime.CompilerServices.Unsafe.AsRef<Union1.__impl_>(in this._impl)._c1);
        }
        [global::System.Diagnostics.CodeAnalysis.UnscopedRef]
        public ref readonly (int a, string b) e
        {
            [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
            get => ref global::System.Runtime.CompilerServices.Unsafe.AsRef<Union1.__impl_>(in this._impl)._f0_0;
        }

        [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
        public bool Equals(VariantHView other) =>
            global::System.Collections.Generic.EqualityComparer<int>.Default.Equals(a, other.a) &&
            global::System.Collections.Generic.EqualityComparer<int>.Default.Equals(b, other.b) &&
            global::System.Collections.Generic.EqualityComparer<string>.Default.Equals(c, other.c) &&
            global::System.Collections.Generic.EqualityComparer<global::System.Collections.Generic.HashSet<int>>.Default.Equals(d, other.d) &&
            global::System.Collections.Generic.EqualityComparer<(int a, string b)>.Default.Equals(e, other.e);

        [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
        public override int GetHashCode() => global::System.HashCode.Combine(a, b, c, d, e);

        [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
        public override bool Equals(object? obj) => obj is VariantHView other && Equals(other);

        [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
        public static bool operator ==(VariantHView left, VariantHView right) => Equals(left, right);
        [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
        public static bool operator !=(VariantHView left, VariantHView right) => !Equals(left, right);

        [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
        public int CompareTo(VariantHView other)
        {
            var _0 = global::System.Collections.Generic.Comparer<int>.Default.Compare(a, other.a);
            if (_0 != 0) return _0;
            var _1 = global::System.Collections.Generic.Comparer<int>.Default.Compare(b, other.b);
            if (_1 != 0) return _1;
            var _2 = global::System.Collections.Generic.Comparer<string>.Default.Compare(c, other.c);
            if (_2 != 0) return _2;
            var _3 = global::System.Collections.Generic.Comparer<global::System.Collections.Generic.HashSet<int>>.Default.Compare(d, other.d);
            if (_3 != 0) return _3;
            var _4 = global::System.Collections.Generic.Comparer<(int a, string b)>.Default.Compare(e, other.e);
            if (_4 != 0) return _4;
            return 0;
        }

        [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
        public static bool operator <(VariantHView left, VariantHView right) => left.CompareTo(right) < 0;
        [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
        public static bool operator >(VariantHView left, VariantHView right) => left.CompareTo(right) > 0;
        [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
        public static bool operator <=(VariantHView left, VariantHView right) => left.CompareTo(right) <= 0;
        [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
        public static bool operator >=(VariantHView left, VariantHView right) => left.CompareTo(right) >= 0;

        [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
        public override string ToString() => $"{nameof(Union1)}.{nameof(Tags.H)} {{ a = {a}, b = {b}, c = {c}, d = {d}, e = {e} }}";

        [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
        public static implicit operator VariantH(VariantHView v) => new VariantH(v.a, v.b, v.c, v.d, v.e);
    }

    [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
    public static Union1 MakeA(int value)
    {
        var _impl = new __impl_(Tags.A);
        _impl._u._0 = value;
        return new Union1(_impl);
    }
    [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
    public static Union1 MakeB(string value)
    {
        var _impl = new __impl_(Tags.B);
        _impl._c0 = value;
        return new Union1(_impl);
    }
    [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
    public static Union1 MakeC(bool value)
    {
        var _impl = new __impl_(Tags.C);
        _impl._u._1 = value;
        return new Union1(_impl);
    }
    [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
    public static Union1 MakeD((int a, int b) value)
    {
        var _impl = new __impl_(Tags.D);
        _impl._u._2 = value;
        return new Union1(_impl);
    }
    [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
    public static Union1 MakeE()
    {
        var _impl = new __impl_(Tags.E);
        return new Union1(_impl);
    }
    [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
    public static Union1 MakeF(global::System.Collections.Generic.List<int>? value)
    {
        var _impl = new __impl_(Tags.F);
        _impl._c0 = value;
        return new Union1(_impl);
    }
    [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
    public static Union1 MakeG((int a, string b) value)
    {
        var _impl = new __impl_(Tags.G);
        _impl._f0_0 = value;
        return new Union1(_impl);
    }
    [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
    public static Union1 MakeH(int a, int b, string c, global::System.Collections.Generic.HashSet<int> d, (int a, string b) e)
    {
        var _impl = new __impl_(Tags.H);
        _impl._u._3._0 = a;
        _impl._u._3._1 = b;
        _impl._c0 = c;
        _impl._c1 = d;
        _impl._f0_0 = e;
        return new Union1(_impl);
    }

    public readonly bool IsA
    {
        [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
        get => this._impl._tag == Tags.A;
    }
    public readonly bool IsB
    {
        [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
        get => this._impl._tag == Tags.B;
    }
    public readonly bool IsC
    {
        [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
        get => this._impl._tag == Tags.C;
    }
    public readonly bool IsD
    {
        [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
        get => this._impl._tag == Tags.D;
    }
    public readonly bool IsE
    {
        [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
        get => this._impl._tag == Tags.E;
    }
    public readonly bool IsF
    {
        [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
        get => this._impl._tag == Tags.F;
    }
    public readonly bool IsG
    {
        [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
        get => this._impl._tag == Tags.G;
    }
    public readonly bool IsH
    {
        [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
        get => this._impl._tag == Tags.H;
    }

    [global::System.Diagnostics.CodeAnalysis.UnscopedRef]
    public ref readonly int A
    {
        [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
        get => ref !this.IsA ? ref global::System.Runtime.CompilerServices.Unsafe.NullRef<int>() : ref this._impl._u._0!;
    }
    [global::System.Diagnostics.CodeAnalysis.UnscopedRef]
    public ref readonly string B
    {
        [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
        get => ref !this.IsB ? ref global::System.Runtime.CompilerServices.Unsafe.NullRef<string>() : ref global::System.Runtime.CompilerServices.Unsafe.As<object?, string>(ref global::System.Runtime.CompilerServices.Unsafe.AsRef<Union1.__impl_>(in this._impl)._c0);
    }
    [global::System.Diagnostics.CodeAnalysis.UnscopedRef]
    public ref readonly bool C
    {
        [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
        get => ref !this.IsC ? ref global::System.Runtime.CompilerServices.Unsafe.NullRef<bool>() : ref this._impl._u._1!;
    }
    [global::System.Diagnostics.CodeAnalysis.UnscopedRef]
    public ref readonly (int a, int b) D
    {
        [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
        get => ref !this.IsD ? ref global::System.Runtime.CompilerServices.Unsafe.NullRef<(int a, int b)>() : ref this._impl._u._2!;
    }
    [global::System.Diagnostics.CodeAnalysis.UnscopedRef]
    public ref readonly global::System.Collections.Generic.List<int>? F
    {
        [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
        get => ref !this.IsF ? ref global::System.Runtime.CompilerServices.Unsafe.NullRef<global::System.Collections.Generic.List<int>?>() : ref global::System.Runtime.CompilerServices.Unsafe.As<object?, global::System.Collections.Generic.List<int>?>(ref global::System.Runtime.CompilerServices.Unsafe.AsRef<Union1.__impl_>(in this._impl)._c0);
    }
    [global::System.Diagnostics.CodeAnalysis.UnscopedRef]
    public ref readonly (int a, string b) G
    {
        [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
        get => ref !this.IsG ? ref global::System.Runtime.CompilerServices.Unsafe.NullRef<(int a, string b)>() : ref this._impl._f0_0!;
    }
    [global::System.Diagnostics.CodeAnalysis.UnscopedRef]
    public ref readonly VariantHView H
    {
        [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
        get => ref !this.IsH ? ref global::System.Runtime.CompilerServices.Unsafe.NullRef<VariantHView>() : ref global::System.Runtime.CompilerServices.Unsafe.As<__impl_, VariantHView>(ref global::System.Runtime.CompilerServices.Unsafe.AsRef<Union1.__impl_>(in this._impl));
    }

    [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
    public readonly bool Equals(Union1 other) => this.Tag != other.Tag ? false : this.Tag switch
    {
        Tags.A => global::System.Collections.Generic.EqualityComparer<int>.Default.Equals(this.A, other.A),
        Tags.B => global::System.Collections.Generic.EqualityComparer<string>.Default.Equals(this.B, other.B),
        Tags.C => global::System.Collections.Generic.EqualityComparer<bool>.Default.Equals(this.C, other.C),
        Tags.D => global::System.Collections.Generic.EqualityComparer<(int a, int b)>.Default.Equals(this.D, other.D),
        Tags.F => global::System.Collections.Generic.EqualityComparer<global::System.Collections.Generic.List<int>?>.Default.Equals(this.F, other.F),
        Tags.G => global::System.Collections.Generic.EqualityComparer<(int a, string b)>.Default.Equals(this.G, other.G),
        Tags.H => this.H.Equals(other.H),
        _ => true,
    };

    [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
    public readonly override int GetHashCode() => this.Tag switch
    {
        Tags.A => global::System.HashCode.Combine(this.Tag, this.A),
        Tags.B => global::System.HashCode.Combine(this.Tag, this.B),
        Tags.C => global::System.HashCode.Combine(this.Tag, this.C),
        Tags.D => global::System.HashCode.Combine(this.Tag, this.D),
        Tags.F => global::System.HashCode.Combine(this.Tag, this.F),
        Tags.G => global::System.HashCode.Combine(this.Tag, this.G),
        Tags.H => global::System.HashCode.Combine(this.Tag, this.H),
        _ => global::System.HashCode.Combine(this.Tag),
    };

    [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
    public readonly override bool Equals(object? obj) => obj is Union1 other && Equals(other);

    [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
    public static bool operator ==(Union1 left, Union1 right) => Equals(left, right);
    [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
    public static bool operator !=(Union1 left, Union1 right) => !Equals(left, right);

    [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
    public readonly int CompareTo(Union1 other) => this.Tag != other.Tag ? global::System.Collections.Generic.Comparer<Tags>.Default.Compare(this.Tag, other.Tag) : this.Tag switch
    {
        Tags.A => global::System.Collections.Generic.Comparer<int>.Default.Compare(this.A, other.A),
        Tags.B => global::System.Collections.Generic.Comparer<string>.Default.Compare(this.B, other.B),
        Tags.C => global::System.Collections.Generic.Comparer<bool>.Default.Compare(this.C, other.C),
        Tags.D => global::System.Collections.Generic.Comparer<(int a, int b)>.Default.Compare(this.D, other.D),
        Tags.F => global::System.Collections.Generic.Comparer<global::System.Collections.Generic.List<int>?>.Default.Compare(this.F, other.F),
        Tags.G => global::System.Collections.Generic.Comparer<(int a, string b)>.Default.Compare(this.G, other.G),
        Tags.H => this.H.CompareTo(other.H),
        _ => 0,
    };

    [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
    public static bool operator <(Union1 left, Union1 right) => left.CompareTo(right) < 0;
    [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
    public static bool operator >(Union1 left, Union1 right) => left.CompareTo(right) > 0;
    [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
    public static bool operator <=(Union1 left, Union1 right) => left.CompareTo(right) <= 0;
    [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
    public static bool operator >=(Union1 left, Union1 right) => left.CompareTo(right) >= 0;

    [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
    public readonly override string ToString() => this.Tag switch
    {
        Tags.A => $"{nameof(Union1)}.{nameof(Tags.A)} {{ {(this.A)} }}",
        Tags.B => $"{nameof(Union1)}.{nameof(Tags.B)} {{ {(this.B)} }}",
        Tags.C => $"{nameof(Union1)}.{nameof(Tags.C)} {{ {(this.C)} }}",
        Tags.D => $"{nameof(Union1)}.{nameof(Tags.D)} {{ {(this.D)} }}",
        Tags.E => $"{nameof(Union1)}.{nameof(Tags.E)}",
        Tags.F => $"{nameof(Union1)}.{nameof(Tags.F)} {{ {(this.F)} }}",
        Tags.G => $"{nameof(Union1)}.{nameof(Tags.G)} {{ {(this.G)} }}",
        Tags.H => $"{(this.H)}",
        _ => nameof(Union1),
    };
}

How to use

You can manually determine the Tag or use pattern matching.
But remember C# does not have enum exhaustion semantics.

var u = Union1.MakeA(123);

if (u is { Tag: Union1.Tags.A, A: var a }) { }

if (u is { IsA: true, A: var a }) { }

if (u.IsA)
{
    var a = u.A;
}

switch (u.Tag)
{
    case Union1.Tags.A:
        break;
  ...
}

switch (u.Tag)
{
    case { IsA: true, A: var a }:
        break;
  ...
}