N.SourceGenerators.UnionTypes 0.27.0

dotnet add package N.SourceGenerators.UnionTypes --version 0.27.0
NuGet\Install-Package N.SourceGenerators.UnionTypes -Version 0.27.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="N.SourceGenerators.UnionTypes" Version="0.27.0" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add N.SourceGenerators.UnionTypes --version 0.27.0
#r "nuget: N.SourceGenerators.UnionTypes, 0.27.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.
// Install N.SourceGenerators.UnionTypes as a Cake Addin
#addin nuget:?package=N.SourceGenerators.UnionTypes&version=0.27.0

// Install N.SourceGenerators.UnionTypes as a Cake Tool
#tool nuget:?package=N.SourceGenerators.UnionTypes&version=0.27.0

N.SourceGenerators.UnionTypes

Discriminated union type source generator

Motivation

C# doesn't support discriminated unions yet. This source generator helps automate writing union types with set of helper methods.

Getting Started

Add package reference to N.SourceGenerators.UnionTypes

dotnet add package N.SourceGenerators.UnionTypes

Create a partial class or struct that will be used as a union type

public partial class FooResult
{
}

Add types you want to use in a discriminated union

public record Success(int Value);
public record ValidationError(string Message);
public record NotFoundError;

public partial class FooResult
{
}

Add N.SourceGenerators.UnionTypes.UnionTypeAttribute to a union type.

using N.SourceGenerators.UnionTypes;

public record Success(int Value);
public record ValidationError(string Message);
public record NotFoundError;

[UnionType(typeof(Success))]
[UnionType(typeof(ValidationError))]
[UnionType(typeof(NotFoundError))]
public partial class FooResult
{
}

Or you can use generic type.

public partial class OperationDataResult<[GenericUnionType] TResult, [GenericUnionType] TError>
{
}

// extend generic type union with additional Int32 type
[UnionType(typeof(int))]
public partial class ExtendedOperationDataResult<[GenericUnionType] TResult, [GenericUnionType] TError>
{
}

Null values are not allowed by default. This behavior can be overriden by AllowNull = true parameter.

[UnionType(typeof(int?), AllowNull = true)]
[UnionType(typeof(string), AllowNull = true)]
public partial class ResultNullable<[GenericUnionType(AllowNull = true)] T>
{
}

Examples

All examples can be found in examples project

Basic

Implicit conversion

public FooResult ImplicitReturn()
{
    // you can return any union type variation without creating FooResult
    return new NotFoundError();
}

Explicit conversion

public ValidationError ExplicitCast(FooResult result)
{
    return (ValidationError)result;
}

Checking value type

public void ValueTypeProperty()
{
    FooResult foo = GetFoo();
    Type valueType = foo.ValueType; // returns typeof(NotFoundError)

    static FooResult GetFoo()
    {
        return new NotFoundError();
    }
}

TryGet method is used to check if union contains a specific type

public void TryGetValue()
{
    FooResult foo = GetFoo();
    if (foo.TryGetNotFoundError(out var notFoundError))
    {
        // make something with notFoundError
    }

    static FooResult GetFoo()
    {
        return new NotFoundError();
    }
}

Alias for each variant is generated based on type name. Use alias parameter to override it.

[UnionType(typeof(int))]
[UnionType(typeof(string))]
// default alias is 'ArrayOfTupleOfIntAndString' but it is overriden by alias parameter
[UnionType(typeof(Tuple<int,string>[]), alias: "Items")]
public partial class AliasResult
{
}

Handle all variants

Match and MatchAsync methods are used to convert union type to another type. These methods force you to handle all possible variations.

public IActionResult MatchMethod(FooResult result)
{
    return result.Match<IActionResult>(
        success => new OkResult(),
        validationError => new BadRequestResult(),
        notFoundError => new NotFoundResult()
    );
}

public async Task<IActionResult> MatchAsyncMethod(FooResult result, CancellationToken cancellationToken)
{
    return await result.MatchAsync<IActionResult>(
        static async (success, ct) =>
        {
            await SomeWork(success, ct);
            return new OkResult();
        }, static async (validationError, ct) =>
        {
            await SomeWork(validationError, ct);
            return new BadRequestResult();
        }, static async (notFoundError, ct) =>
        {
            await SomeWork(notFoundError, ct);
            return new NotFoundResult();
        }, cancellationToken);

    static Task SomeWork<T>(T value, CancellationToken ct)
    {
        return Task.Delay(100, ct);
    }
}

Switch and SwitchAsync methods are used to execute some work based on inner type

 public void SwitchMethod(FooResult result)
{
    result.Switch(
        success => SomeWork(success),
        validationError => SomeWork(validationError),
        notFoundError => SomeWork(notFoundError)
    );

    static void SomeWork<T>(T value)
    {
        throw new NotImplementedException();
    }
}

public async Task SwitchAsyncMethod(FooResult result, CancellationToken cancellationToken)
{
    await result.SwitchAsync(
        static async (success, ct) =>
        {
            await SomeWork(success, ct);
        }, static async (validationError, ct) =>
        {
            await SomeWork(validationError, ct);
        }, static async (notFoundError, ct) =>
        {
            await SomeWork(notFoundError, ct);
        }, cancellationToken);

    static Task SomeWork<T>(T value, CancellationToken ct)
    {
        return Task.Delay(100, ct);
    }
}

JSON serialization (EXPERIMENTAL)

To add JSON support

  • add JsonPolymorphicUnion attribute to union type
  • add TypeDiscriminator to each type variant
Limitations:
  • .NET 7 or newer
  • only complex type variants
Example
[UnionType(typeof(JsonTestsFooJ), TypeDiscriminator = "Foo")]
[UnionType(typeof(JsonTestsBarJ), TypeDiscriminator = "Bar")]
[JsonPolymorphicUnion]
public partial class JsonTestsUnion
{
}

Union to union converter

When one union type's variants is subset of another union type's variants use one of the following attributes to convert one type to another: UnionConverterTo, UnionConverterFrom, or UnionConverter.

[UnionConverterFrom(typeof(DataAccessResult))] // use this attribute
public partial class BusinessLogicResult
{
}

[UnionConverterTo(typeof(BusinessLogicResult))] // OR this
public partial class DataAccessResult
{
}

[UnionConverter(typeof(DataAccessResult), typeof(BusinessLogicResult))] // OR this
public static partial class Converters
{
}

public class Repository
{
    public DataAccessResult UpdateItem()
    {
        return new NotFoundError();
    }
}

public class Service
{
    private readonly Repository _repository;

    public BusinessLogicResult Update()
    {
        var isValid = IsValid();
        if (!isValid)
        {
            return new ValidationError("the item is not valid");
        }

        var repositoryResult = _repository.UpdateItem();
        // implicit conversion DataAccessResult to BusinessLogicResult when `UnionConverterTo` or `UnionConverterFrom` attribute is used
        return repositoryResult;
        // OR extension method when UnionConverter attribute is used
        return repositoryResult.Convert();
    }

    private bool IsValid() => throw new NotImplementedException();
}
There are no supported framework assets in this package.

Learn more about Target Frameworks and .NET Standard.

  • .NETStandard 2.0

    • No dependencies.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on N.SourceGenerators.UnionTypes:

Package Downloads
Zomp.SyncMethodGenerator

Generates synchronized method from async method

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
0.27.0 704 3/19/2024
0.27.0-rc.58 35 3/19/2024
0.27.0-rc.57 59 3/13/2024
0.26.0 14,402 10/26/2023
0.26.0-rc.55 48 11/16/2023
0.26.0-rc.53 57 10/30/2023
0.26.0-rc.52 56 10/30/2023
0.26.0-rc.51 60 10/30/2023
0.26.0-rc.50 51 10/30/2023
0.26.0-rc.48 57 10/26/2023
0.25.3 331 10/18/2023
0.25.3-rc.41 58 10/18/2023
0.25.1-rc.40 313 10/17/2023
0.25.0-rc.39 70 10/17/2023
0.24.0 991 10/17/2023
0.24.0-rc.37 105 10/16/2023
0.23.0 675 10/3/2023
0.23.0-rc.35 282 10/2/2023
0.22.0 284 10/2/2023
0.22.0-rc.33 57 10/2/2023
0.21.0 249 10/2/2023
0.21.0-rc.31 58 10/2/2023
0.21.0-rc.30 67 9/28/2023
0.20.0-rc.29 887 9/6/2023
0.20.0-rc.28 42 9/6/2023
0.19.0 640 8/3/2023
0.18.0 485 2/5/2023
0.17.0 324 2/2/2023
0.16.0 297 1/30/2023
0.15.0 309 1/29/2023
0.14.0 308 1/21/2023
0.13.0 327 1/20/2023
0.12.0 343 1/20/2023
0.11.0 329 1/18/2023
0.10.0 331 1/18/2023
0.9.0 306 1/17/2023
0.8.0 318 1/17/2023
0.7.0 298 1/17/2023
0.6.0 302 1/15/2023
0.5.1 327 1/15/2023
0.5.0 348 1/15/2023
0.4.0 356 1/15/2023
0.3.0 376 1/15/2023
0.2.0 363 1/15/2023
0.1.0 407 1/15/2023