Skip to content

vicosanz/UnionOf

Repository files navigation

UnionOf

C# Implementation of Discriminated unions made easy.

UnionOf NuGet Badge

UnionOf.Generator NuGet Badge

publish to nuget

Buy me a coffee

If you want to reward my effort, ☕ https://www.paypal.com/paypalme/vicosanzdev?locale.x=es_XC

All unions are source generated, you must create a struct specifying types to join in three ways:

  1. Using attribute decorating a struct
    [UnionOf(typeof(int), typeof(string))] 
    public readonly partial struct IntOrString
    {
    }

    ...
    IntOrString valorInt = 9;
    IntOrString valorStr = "9";
  1. Inheriting from IUnionOf<>
    [UnionOf]
    public readonly partial struct IntOrString : IUnionOf<int, string>
    {
    }

    ...
    IntOrString valorInt = 9;
    IntOrString valorStr = "9";
  1. Inheriting from pre-generated typed structs
    UnionOf<int, string> valorInt = 9;
    UnionOf<int, string> valorStr = "9";

You can add logic to generated UnionOfs

    [UnionOf]
    public readonly partial struct IntOrString : IUnionOf<int, string>
    {
        // User logic
        public int GetInt() => Value switch
        {
            int number => number,
            string text => ParseString(text),
            _ => throw new InvalidOperationException()
        };

        private static int ParseString(string s) => int.TryParse(s, out int num) ? num : 0;
        // End of User logic
    }
    

You can add define how to handle nulls defining a Default value if null is received

    [UnionOf]
    public readonly partial struct IntOrString : IUnionOf<int, string>, IHandleDefaultValue
    {
        public object ParseNull() => "none";
    }
    
    ...
    IntOrString value1 = new();
    Console.WriteLine(value1); // output: none (default value)

You can use UnionOfs as Result type with signed types

    //Predefined ErrOr type present in UnionOf dll, included here for illustration
    [UnionOf]
    public readonly partial struct ErrOr<T0> : IUnionOf<T0, Exception>, IErrOr
    {
    }
    
    ErrOr<bool> resultbool = ProcessData(false);
    ErrOr<bool> resulterr = ProcessData(true);

    ErrOr<bool> ProcessData(bool fail)
    {
        if (fail) return new AccessViolationException();
        return true;
    }


    //Method to Register Customer, implicit Exception in ErrOr
    ErrOr<bool, List<ValidationError>> result = RegisterCustomer(customer);

    ErrOr<bool, List<ValidationError>> RegisterCustomer(Customer customer)
    {
        try
        {
            if (!customer.Validate(out errors))
            {
                return errors;
            }
            bool newCustomer = RepositoryCustomer.Save(customer);
            return newCustomer;
        }
        catch (Exception ex)
        {
            return ex;
        }
    }

You can include subtypes into struct as result types and specialized methods with additional parameters.

    [UnionOf]
    public readonly partial struct ResultOperation : IUnionOf<ResultOperation.Ok, ResultOperation.Err>
    {
        public record Ok { }
        public static ResultOperation IsOk() => Create(new Ok());


        public record Err(string Message);
        public static ResultOperation IsErr(string message) => Create(new Err(message));

    }

    ...
    ResultOperation operation;
    
    operation = ResultOperation.IsOk();

    or

    operation = ResultOperation.IsErr("Invalid name");

In order to discriminated values use c# match pattern

    [UnionOf]
    public readonly partial struct CatDog : IUnionOf<Cat, Dog>
    {
    }

    public class Cat
    {
        public string Type => "Cat";
    }

    public class Dog
    {
        public string Type => "Dog";

    }

    ...

    CatDog pet = new Dog();
    EvaluatePet(pet);

    static void EvaluatePet(CatDog pet)
    {
        var thisType = pet.Value switch
        {
            Cat cat => cat.Type,
            Dog dog => dog.Type,
            _ => throw new Exception("")
        };

        Console.WriteLine($"Pet is {thisType}");
    }

You can also implement optional pattern

    record Persona(string FirstName, Optional<string> LastName);

    var persona = new Persona("John", "Smith");

    var fullname = persona.LastName
        .Map(_ => $"{persona.FirstName} {persona.LastName}")
        .Reduce(() => persona.FirstName);

    Console.WriteLine(fullname);
    string empty = "";
    var whentest = Optional.Of(empty).WhenNot(string.IsNullOrWhiteSpace).Reduce("is empty");
    Console.WriteLine(whentest);

About

Implementation of Descriminated unions

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages