N.SourceGenerators.UnionTypes
0.28.0
dotnet add package N.SourceGenerators.UnionTypes --version 0.28.0
NuGet\Install-Package N.SourceGenerators.UnionTypes -Version 0.28.0
<PackageReference Include="N.SourceGenerators.UnionTypes" Version="0.28.0" />
paket add N.SourceGenerators.UnionTypes --version 0.28.0
#r "nuget: N.SourceGenerators.UnionTypes, 0.28.0"
// Install N.SourceGenerators.UnionTypes as a Cake Addin #addin nuget:?package=N.SourceGenerators.UnionTypes&version=0.28.0 // Install N.SourceGenerators.UnionTypes as a Cake Tool #tool nuget:?package=N.SourceGenerators.UnionTypes&version=0.28.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();
}
Configuration
Property | Default | Description |
---|---|---|
UnionTypesGenerator_ExcludeFromCodeCoverage | true | Add ExcludeFromCodeCoverage attribute when true |
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<UnionTypesGenerator_ExcludeFromCodeCoverage>false</UnionTypesGenerator_ExcludeFromCodeCoverage>
</PropertyGroup>
</Project>
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.28.0 | 3,514 | 5/2/2024 |
0.28.0-rc.61 | 28 | 5/2/2024 |
0.28.0-rc.60 | 21 | 5/2/2024 |
0.27.0 | 1,491 | 3/19/2024 |
0.27.0-rc.58 | 57 | 3/19/2024 |
0.27.0-rc.57 | 75 | 3/13/2024 |
0.26.0 | 32,051 | 10/26/2023 |
0.26.0-rc.55 | 69 | 11/16/2023 |
0.26.0-rc.53 | 75 | 10/30/2023 |
0.26.0-rc.52 | 73 | 10/30/2023 |
0.26.0-rc.51 | 77 | 10/30/2023 |
0.26.0-rc.50 | 67 | 10/30/2023 |
0.26.0-rc.48 | 74 | 10/26/2023 |
0.25.3 | 365 | 10/18/2023 |
0.25.3-rc.41 | 76 | 10/18/2023 |
0.25.1-rc.40 | 330 | 10/17/2023 |
0.25.0-rc.39 | 88 | 10/17/2023 |
0.24.0 | 1,022 | 10/17/2023 |
0.24.0-rc.37 | 124 | 10/16/2023 |
0.23.0 | 710 | 10/3/2023 |
0.23.0-rc.35 | 300 | 10/2/2023 |
0.22.0 | 317 | 10/2/2023 |
0.22.0-rc.33 | 75 | 10/2/2023 |
0.21.0 | 280 | 10/2/2023 |
0.21.0-rc.31 | 77 | 10/2/2023 |
0.21.0-rc.30 | 84 | 9/28/2023 |
0.20.0-rc.29 | 906 | 9/6/2023 |
0.20.0-rc.28 | 58 | 9/6/2023 |
0.19.0 | 670 | 8/3/2023 |
0.18.0 | 521 | 2/5/2023 |
0.17.0 | 360 | 2/2/2023 |
0.16.0 | 330 | 1/30/2023 |
0.15.0 | 345 | 1/29/2023 |
0.14.0 | 341 | 1/21/2023 |
0.13.0 | 357 | 1/20/2023 |
0.12.0 | 388 | 1/20/2023 |
0.11.0 | 362 | 1/18/2023 |
0.10.0 | 361 | 1/18/2023 |
0.9.0 | 358 | 1/17/2023 |
0.8.0 | 351 | 1/17/2023 |
0.7.0 | 334 | 1/17/2023 |
0.6.0 | 333 | 1/15/2023 |
0.5.1 | 360 | 1/15/2023 |
0.5.0 | 381 | 1/15/2023 |
0.4.0 | 389 | 1/15/2023 |
0.3.0 | 408 | 1/15/2023 |
0.2.0 | 396 | 1/15/2023 |
0.1.0 | 462 | 1/15/2023 |