FiddlyWiki

c#-lang

slug:cs-lang

type: text/html

We have Java at home :^)


Discriminated Union

The kind of thing that you shouldn't be using at work, but I did.


// Go upvote https://stackoverflow.com/a/3199453
public abstract class Union<A, B> { // Casts public static implicit operator Union<A, B>(A value) => new Ayy(value); public static implicit operator Union<A, B>(B value) => new Bee(value); public static explicit operator A(Union<A, B> union) => union.Match( a => a, b => throw new InvalidCastException($"Cannot cast union of underlying type {typeof(A)} to {typeof(B)}") ); public static explicit operator B(Union<A, B> union) => union.Match( a => throw new InvalidCastException($"Cannot cast union of underlying type {typeof(B)} to {typeof(A)}"), b => b ); // Interface public abstract T Match<T>( Func<A, T> matchA, Func<B, T> matchB ); private Union() { } // Convenience public bool IsA => Match( a => true, b => false ); public bool IsB => Match( a => false, b => true ); public void Select( Action<A> selectA, Action<B> selectB ) { Match( a => { selectA(a); return false; }, b => { selectB(b); return false; } ); } // Implementation of A public sealed class Ayy : Union<A, B> { private A value; public Ayy(A value) { this.value = value; } public override T Match<T>( Func<A, T> matchA, Func<B, T> _ ) => matchA(this.value); } // Implementation of B public sealed class Bee : Union<A, B> { private B value; public Bee(B value) { this.value = value; } public override T Match<T>( Func<A, T> _, Func<B, T> matchB ) => matchB(this.value); } }

Some example usage:

var a = new Union<string, int>.Ayy("hello");
var b = new Union<string, int>.Bee(123);
Union<string, int> c = "world";
Union<string, int> d = 456;

void PrintUnion(Union<string, int> union) => Console.WriteLine(union.Match(
    a => $"\"{a}\"",
    b => $"{b}i"
));

PrintUnion(a);
PrintUnion(b);
PrintUnion(c);
PrintUnion(d);

Console.WriteLine((string)a);

try
{
    Console.WriteLine((string)b);
}
catch (Exception ex)
{
    Console.Error.WriteLine($"Caught exception: {ex}");
}

Console.WriteLine(b.IsA ? $"\"{(string)b}\"" : $"{(int)b}");
string? lastString = null;
int? lastInt = null; foreach (var onion in new Union<string, int>[] { a, b, c, d }) { onion.Select( str => { lastString = str; }, intg => { lastInt = intg; } ); } Console.WriteLine($"Last string: {lastString}, last int: {lastInt}");