Skip to content

Repository files navigation

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

publicpartialclassFooResult{}

Add types you want to use in a discriminated union

publicrecordSuccess(intValue);publicrecordValidationError(stringMessage);publicrecordNotFoundError;publicpartialclassFooResult{}

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

usingN.SourceGenerators.UnionTypes;publicrecordSuccess(intValue);publicrecordValidationError(stringMessage);publicrecordNotFoundError;[UnionType(typeof(Success))][UnionType(typeof(ValidationError))][UnionType(typeof(NotFoundError))]publicpartialclassFooResult{}

Or you can use generic type.

publicpartialclassOperationDataResult<[GenericUnionType]TResult,[GenericUnionType]TError>{}// extend generic type union with additional Int32 type[UnionType(typeof(int))]publicpartialclassExtendedOperationDataResult<[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)]publicpartialclassResultNullable<[GenericUnionType(AllowNull=true)]T>{}

Examples

All examples can be found in examples project

Basic

Implicit conversion

publicFooResultImplicitReturn(){// you can return any union type variation without creating FooResultreturnnewNotFoundError();}

Explicit conversion

publicValidationErrorExplicitCast(FooResultresult){return(ValidationError)result;}

Checking value type

publicvoidValueTypeProperty(){FooResultfoo=GetFoo();TypevalueType=foo.ValueType;// returns typeof(NotFoundError)staticFooResultGetFoo(){returnnewNotFoundError();}}

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

publicvoidTryGetValue(){FooResultfoo=GetFoo();if(foo.TryGetNotFoundError(outvarnotFoundError)){// make something with notFoundError}staticFooResultGetFoo(){returnnewNotFoundError();}}

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")]publicpartialclassAliasResult{}

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.

publicIActionResultMatchMethod(FooResultresult){returnresult.Match<IActionResult>(
success =>newOkResult(),
validationError =>newBadRequestResult(),
notFoundError =>newNotFoundResult());}publicasyncTask<IActionResult>MatchAsyncMethod(FooResultresult,CancellationTokencancellationToken){returnawaitresult.MatchAsync<IActionResult>(staticasync(success,ct)=>{awaitSomeWork(success,ct);returnnewOkResult();},staticasync(validationError,ct)=>{awaitSomeWork(validationError,ct);returnnewBadRequestResult();},staticasync(notFoundError,ct)=>{awaitSomeWork(notFoundError,ct);returnnewNotFoundResult();},cancellationToken);staticTaskSomeWork<T>(Tvalue,CancellationTokenct){returnTask.Delay(100,ct);}}

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

publicvoidSwitchMethod(FooResultresult){result.Switch(
success =>SomeWork(success),
validationError =>SomeWork(validationError),
notFoundError =>SomeWork(notFoundError));staticvoidSomeWork<T>(Tvalue){thrownewNotImplementedException();}}publicasyncTaskSwitchAsyncMethod(FooResultresult,CancellationTokencancellationToken){awaitresult.SwitchAsync(staticasync(success,ct)=>{awaitSomeWork(success,ct);},staticasync(validationError,ct)=>{awaitSomeWork(validationError,ct);},staticasync(notFoundError,ct)=>{awaitSomeWork(notFoundError,ct);},cancellationToken);staticTaskSomeWork<T>(Tvalue,CancellationTokenct){returnTask.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]publicpartialclassJsonTestsUnion{}

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 attributepublicpartialclassBusinessLogicResult{}[UnionConverterTo(typeof(BusinessLogicResult))]// OR thispublicpartialclassDataAccessResult{}[UnionConverter(typeof(DataAccessResult),typeof(BusinessLogicResult))]// OR thispublicstaticpartialclassConverters{}publicclassRepository{publicDataAccessResultUpdateItem(){returnnewNotFoundError();}}publicclassService{privatereadonlyRepository_repository;publicBusinessLogicResultUpdate(){varisValid=IsValid();if(!isValid){returnnewValidationError("the item is not valid");}varrepositoryResult=_repository.UpdateItem();// implicit conversion DataAccessResult to BusinessLogicResult when `UnionConverterTo` or `UnionConverterFrom` attribute is usedreturnrepositoryResult;// OR extension method when UnionConverter attribute is usedreturnrepositoryResult.Convert();}privateboolIsValid()=>thrownewNotImplementedException();}

Configuration

PropertyDefaultDescription
UnionTypesGenerator_ExcludeFromCodeCoveragetrueAdd ExcludeFromCodeCoverage attribute when true
<ProjectSdk="Microsoft.NET.Sdk">
<PropertyGroup>
<UnionTypesGenerator_ExcludeFromCodeCoverage>false</UnionTypesGenerator_ExcludeFromCodeCoverage>
</PropertyGroup>
</Project>

About

Discriminated union type source generator

Topics

Resources

Contributing

Stars

11 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages