Skip to main content

N.SourceGenerators.UnionTypes by Alexey Sosnin

Nuget / site data

Nuget GitHub last commit GitHub Repo stars

Details

Info

info

Name: N.SourceGenerators.UnionTypes

Discriminated union type source generator

Author: Alexey Sosnin

NuGet: https://www.nuget.org/packages/N.SourceGenerators.UnionTypes/

You can find more details at https://github.com/Ne4to/N.SourceGenerators.UnionTypes

Source : https://github.com/Ne4to/N.SourceGenerators.UnionTypes

Original Readme

note

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();
}

About

note

Generating different union types

How to use

Example ( source csproj, source files )

This is the CSharp Project that references N.SourceGenerators.UnionTypes

<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="N.SourceGenerators.UnionTypes" Version="0.26.0" />
</ItemGroup>
<PropertyGroup>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)\GX</CompilerGeneratedFilesOutputPath>
</PropertyGroup>
</Project>

Generated Files

Those are taken from $(BaseIntermediateOutputPath)\GX

// <auto-generated>
// This code was generated by https://github.com/Ne4to/N.SourceGenerators.UnionTypes
// Feel free to open an issue
// </auto-generated>
#nullable enable
using System;
using System.Runtime.CompilerServices;

namespace N.SourceGenerators.UnionTypes
{
[AttributeUsage(AttributeTargets.GenericParameter, Inherited = false, AllowMultiple = false)]
internal sealed class GenericUnionTypeAttribute : Attribute
{
public string? Alias { get; set; }
public bool AllowNull { get; set; }
public object? TypeDiscriminator { get; set; }
}
}

Usefull

Download Example (.NET C# )

Share N.SourceGenerators.UnionTypes

https://ignatandrei.github.io/RSCG_Examples/v2/docs/N.SourceGenerators.UnionTypes

In the same category (FunctionalProgramming) - 10 other generators

cachesourcegenerator

dunet

Funcky.DiscriminatedUnion

FunicularSwitch

OneOf

PartiallyApplied

RSCG_Utils_Memo

TypeUtilities

UnionGen

UnionsGenerator