Sundew.DiscriminatedUnions 5.3.3

This package has a SemVer 2.0.0 package version: 5.3.3+44443ec170.
There is a newer version of this package available.
See the version list below for details.
dotnet add package Sundew.DiscriminatedUnions --version 5.3.3
NuGet\Install-Package Sundew.DiscriminatedUnions -Version 5.3.3
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="Sundew.DiscriminatedUnions" Version="5.3.3" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Sundew.DiscriminatedUnions --version 5.3.3
#r "nuget: Sundew.DiscriminatedUnions, 5.3.3"
#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 Sundew.DiscriminatedUnions as a Cake Addin
#addin nuget:?package=Sundew.DiscriminatedUnions&version=5.3.3

// Install Sundew.DiscriminatedUnions as a Cake Tool
#tool nuget:?package=Sundew.DiscriminatedUnions&version=5.3.3

Discriminated Unions

Sundew.DiscriminatedUnions implement discriminated unions for C#, until a future version of C# provides it out of the box. The idea is that this package can be deleted once unions are supported in C#, without requiring changes to switch expressions and statements.

In addition, the project supports dimensional unions through default interface methods (traits). A dimensional union is a union where cases can be reused in any number of unions, by supporting interface unions through the possibility of implementing multiple interface and default interface members.

How it works

A Roslyn analyzer asserts and report errors in case switch statements or switch expression do not handle all cases. C# 8 and 9 already comes with great pattern matching support for evaluation.

In order that the inheritance hierarchy remain closed (All cases in the same assembly), an analyzer ensures that unions are not derived from in referencing assemblies. Similarly all case classes should be sealed.

Create a union by inheriting from an abstract base (record) class (or interface) marked with the DiscriminatedUnion attribute to build various cases. Either specify the partial keyword to the union for a source generator to implement factory methods or use the codefix PDU0001 to generate them.

Sample

Defining a union

[Sundew.DiscriminatedUnions.DiscriminatedUnion]
public abstract partial record Result
{
    public sealed record Success : Result;

    public sealed record Warning(string Message) : Result;

    public sealed record Error(int Code) : Result;
}

Alternatively, a union can be defined with unnested case classes and interfaces, allowing the possibility of creating dimensional unions (see below).

Evaluation

var message = result switch
{
    Result.Error { Code: > 70 } error => $"High Error code: {error.Code}",
    Result.Error error => $"Error code: {error.Code}",
    Result.Warning { Message: "Tough warning" } => "Not good",
    Result.Warning warning => warning.Message,
    Result.Success => "Great",
};

Dimensional unions

To support dimensional unions, unnested cases help because the cases are no longer defined inside a union. However, for this to work the unions are required to declare a factory method named exactly like the case type and that has the CaseType attribute specifying the actual type. Since version 3, factory methods are generated when the union is declared partial. Alternatively, a code fix (PDU0001) is available to generate the factory methods.

[Sundew.DiscriminatedUnions.DiscriminatedUnion]
public partial interface IExpression;

[Sundew.DiscriminatedUnions.DiscriminatedUnion]
public partial interface IArithmeticExpression : IExpression;

[Sundew.DiscriminatedUnions.DiscriminatedUnion]
public partial interface ICommutativeExpression : IArithmeticExpression;

public sealed record AdditionExpression(IExpression Lhs, IExpression Rhs) : ICommutativeExpression;

public sealed record SubtractionExpression(IExpression Lhs, IExpression Rhs) : IArithmeticExpression;

public sealed record MultiplicationExpression(IExpression Lhs, IExpression Rhs) : ICommutativeExpression;

public sealed record DivisionExpression(IExpression Lhs, IExpression Rhs) : IArithmeticExpression;

public sealed record ValueExpression(int Value) : IExpression;
Evaluating dimensional unions

With dimensional unions it is possible to handle all cases using a sub union. As seen in the example below, handling the ArithmeticExpression covers Addition-, Subtraction-, Multiplication- and DivisionExpression. Typically one would dispatch these to a method handling ArithmeticExpression and where handling all cases would be checked, but it is not required. This makes it convienient to separate handling logic in smaller chucks of code.

public int Evaluate(Expression expression)
{
    return expression switch
        {
            ArithmeticExpression arithmeticExpression => Evaluate(arithmeticExpression),
            ValueExpression valueExpression => valueExpression.Value,
        };
}

public int Evaluate(ArithmeticExpression arithmeticExpression)
{
    return arithmeticExpression switch
        {
            AdditionExpression additionExpression => Evaluate(additionExpression.Lhs) + Evaluate(additionExpression.Rhs),
            SubtractionExpression subtractionExpression => Evaluate(subtractionExpression.Lhs) - Evaluate(subtractionExpression.Rhs),
            MultiplicationExpression multiplicationExpression => Evaluate(multiplicationExpression.Lhs) * Evaluate(multiplicationExpression.Rhs),
            DivisionExpression divisionExpression => Evaluate(divisionExpression.Lhs) / Evaluate(divisionExpression.Rhs),
        };
}
Enum evaluation

As of version 5.1, regular enums can also use the DiscriminatedUnion attribute causing the analyzer to exhaustively check switch statements and expressions.

Generator features

As mentioned a source generator is automatically activated for generating factory methods when the partial keyword is specified. In addition, the DiscriminatedUnion attribute can specify a flags enum (GeneratorFeatures) to control additional code generation.

  • Segregate - Generates an extension method for IEnumerable<TUnion> that segregates all items into buckets of the different result.

Supported diagnostics:

Diagnostic Id Description Code Fix
SDU0001 Switch does not handled all cases yes
SDU0002 Switch should not handle default case yes
SDU0003 Switch has unreachable null case yes
SDU0004 Class unions must be abstract yes
SDU0005 Only unions can extended other unions no
SDU0006 Unions cannot be extended outside their assembly no
SDU0007 Cases must be declared in the same assembly as their unions no
SDU0008 Cases should be sealed yes
SDU0009 Unnested cases should have factory method PDU0001
SDU0010 Factory method should have correct CaseTypeAttribute yes
PDU0001 Make union partial for code generator yes
PDU0002 Populate union factory methods yes
SDU9999 Switch should throw in default case no
GDU0001 Discriminated union declaration could not be found no

Issues/Todos

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
.NET Core netcoreapp1.0 was computed.  netcoreapp1.1 was computed.  netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard1.0 is compatible.  netstandard1.1 was computed.  netstandard1.2 was computed.  netstandard1.3 was computed.  netstandard1.4 was computed.  netstandard1.5 was computed.  netstandard1.6 was computed.  netstandard2.0 was computed.  netstandard2.1 was computed. 
.NET Framework net45 was computed.  net451 was computed.  net452 was computed.  net46 was computed.  net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen30 was computed.  tizen40 was computed.  tizen60 was computed. 
Universal Windows Platform uap was computed.  uap10.0 was computed. 
Windows Phone wp8 was computed.  wp81 was computed.  wpa81 was computed. 
Windows Store netcore was computed.  netcore45 was computed.  netcore451 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

This package has no dependencies.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on Sundew.DiscriminatedUnions:

Package Downloads
Sundew.Base.Collections.Linq

Extensions for collections types.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
5.3.5 74 4/22/2024
5.3.5-u20240421-235601-ci 34 4/21/2024
5.3.4 59 4/10/2024
5.3.3 76 3/29/2024
5.3.2 62 3/25/2024
5.3.1 175 3/6/2024
5.3.0 41 3/6/2024
5.2.8 394 2/17/2024
5.2.7 79 2/15/2024
5.2.6 188 1/18/2024
5.2.5 184 1/11/2024
5.2.4 58 1/10/2024
5.2.3 108 12/20/2023
5.2.2 131 12/18/2023
5.2.1 174 12/14/2023
5.2.0 119 12/5/2023
5.2.0-u20231205-000421-ci 46 12/5/2023
5.1.1 68 11/30/2023
5.1.1-u20231126-231813-ci 54 11/26/2023
5.1.1-u20231126-231730-ci 46 11/26/2023
5.1.0 195 11/26/2023
5.1.0-u20231126-215035-ci 44 11/26/2023
5.1.0-u20231016-124505-ci 75 10/16/2023
5.1.0-u20231012-025617-ci 56 10/12/2023
5.0.1-u20231010-163921-ci 56 10/10/2023
5.0.0 244 9/2/2023
5.0.0-u20230902-100936-ci 66 9/2/2023
4.0.0-u20230521-020711-ci 65 5/21/2023
3.1.1 206 4/25/2023
3.1.1-u20230425-224727-ci 68 4/25/2023
3.1.0 99 4/25/2023
3.1.0-u20230425-202906-ci 87 4/25/2023
3.0.6 127 4/4/2023
3.0.6-u20230404-191032-ci 77 4/4/2023
3.0.5 86 4/4/2023
3.0.5-u20230404-184749-ci 77 4/4/2023
3.0.4 131 3/21/2023
3.0.4-u20230321-202732-ci 87 3/21/2023
3.0.3 322 3/19/2023
3.0.3-u20230319-233538-ci 82 3/19/2023
3.0.3-u20230319-232943-ci 84 3/19/2023
3.0.3-u20230319-232100-ci 78 3/19/2023
3.0.3-u20230319-231811-ci 79 3/19/2023
3.0.2 93 3/19/2023
3.0.2-u20230319-225014-ci 80 3/19/2023
3.0.2-u20230319-215828-ci 81 3/19/2023
3.0.2-u20230317-062413-ci 80 3/17/2023
3.0.1 139 3/9/2023
3.0.1-u20230309-230315-ci 92 3/9/2023
3.0.1-u20230308-215731-ci 79 3/8/2023
3.0.0-u20230308-001125-ci 79 3/8/2023
3.0.0-u20230307-232317-ci 79 3/7/2023
3.0.0-u20230307-221726-ci 78 3/7/2023
3.0.0-u20230307-221331-ci 76 3/7/2023
3.0.0-u20230307-204245-ci 77 3/7/2023
3.0.0-u20230307-202907-ci 83 3/7/2023
2.1.0-u20230306-072830-ci 82 3/6/2023
2.1.0-u20230306-001109-ci 79 3/6/2023
2.1.0-u20230305-023406-ci 82 3/5/2023
2.1.0-u20230304-215402-ci 80 3/4/2023
2.0.6 256 1/7/2023
2.0.6-u20230107-121511-ci 92 1/7/2023
2.0.5 118 12/30/2022
2.0.5-u20221230-134347-ci 85 12/30/2022
2.0.4 302 11/29/2022
2.0.4-u20221129-211429-ci 82 11/29/2022
2.0.3 86 11/29/2022
2.0.3-u20221129-204956-ci 82 11/29/2022
2.0.2 87 11/29/2022
2.0.2-u20221129-203222-ci 80 11/29/2022
2.0.1 91 11/28/2022
2.0.1-u20221128-230952-ci 81 11/28/2022
2.0.0 316 11/26/2022
2.0.0-u20221126-201223-ci 83 11/26/2022
2.0.0-u20221122-214246-ci 133 11/22/2022
2.0.0-u20221122-202007-ci 93 11/22/2022
1.0.6-u20221119-224905-ci 88 11/19/2022
1.0.5 123 8/31/2022
1.0.5-u20220831-221815-ci 96 8/31/2022
1.0.4 105 8/31/2022
1.0.4-u20220831-215231-ci 91 8/31/2022
1.0.3 111 8/30/2022
1.0.3-u20220830-222242-ci 99 8/30/2022
1.0.2 122 8/21/2022
1.0.2-u20220821-113142-ci 112 8/21/2022
1.0.2-u20220821-113030-ci 113 8/21/2022
1.0.2-u20220812-182737-ci 111 8/12/2022
1.0.2-u20220711-200756-ci 125 7/11/2022
1.0.2-u20220711-200621-ci 121 7/11/2022
1.0.2-u20220623-215317-ci 115 6/23/2022
1.0.2-u20220619-212948-ci 116 6/19/2022
1.0.1 130 6/18/2022
1.0.1-u20220618-075040-ci 119 6/18/2022
1.0.0 171 1/10/2022
1.0.0-u20220110-135616-ci 136 1/10/2022
0.1.1-u20220110-135152-ci 139 1/10/2022
0.1.1-u20211120-051824-ci 431 11/20/2021
0.1.1-u20211111-233448-ci 171 11/11/2021
0.1.1-u20211107-215103-ci 156 11/7/2021
0.1.1-u20211107-214928-ci 152 11/7/2021
0.1.1-u20211107-001231-ci 269 11/7/2021
0.1.1-u20211107-000016-ci 274 11/7/2021
0.1.1-u20211014-220456-ci 200 10/14/2021
0.1.1-u20211014-080334-ci 148 10/14/2021
0.1.1-u20211012-230901-ci 179 10/12/2021
0.1.1-u20211011-230626-ci 200 10/11/2021
0.1.1-u20211010-204752-ci 238 10/10/2021
0.1.1-u20211009-224749-ci 320 10/9/2021
0.1.1-u20211008-223345-ci 210 10/8/2021
0.1.1-u20211007-132749-ci 185 10/7/2021
0.1.1-u20211007-123829-ci 147 10/7/2021
0.1.0 349 10/3/2021
0.1.0-u20211003-170424-ci 311 10/3/2021
0.1.0-u20210927-215139-ci 144 9/27/2021

2.0 - Support matching unions in referenced projects, marker attributes included as source rather than library reference.
1.0 - Initial version