Discriminated Unions in C#

Hi all. Among the many interesting concepts available in F#, Discriminated Unions attracted me. I wondered how to implement them in C#, because it lacks (syntactic) support for union types, and I decided to find a way to imitate them.

Discriminated Unions – a data type that is a discriminated union, each of which can consist of its own data types (also named).

The idea is that we have limited number of options to choose from, and each option can consist of your datasetno way unrelated with others, but all variants united common subtype.

We will use this idea to create our own Discriminated Unions

Implementation

The “benchmark” will be the implementation in F#

type Worker =
    | Developer of KnownLanguages: string seq
    | Manager of MaintainedProjectsCount: int
    | Tester of UnitTestsPerHour: double

Now implementation in C#

public abstract record RecordWorker
{   
    private RecordWorker(){ }
    public record Developer(IEnumerable<string> KnownLanguages): RecordWorker { }

    public record Manager(int MaintainedProjectsCount) : RecordWorker;

    public record Tester(double UnitTestsPerHour) : RecordWorker;
}

This implementation fits the criteria described above:

  1. Limited set of choices – all choices – inside another class with a private constructor

  2. Each variant consists of its own set of data – each variant is a separate class

  3. United by a common name/subtype – all inherit the base abstract class

In this implementation, I used record, because they allow you to write less code and are very similar in behavior to Discriminated Unions

Usage

F# function using our type

let getWorkerInfo (worker: Worker) =
    match worker with
    | Developer knownLanguages -> 
    				$"Known languages: %s{String.Join(',', knownLanguages)}"
    | Manager maintainedProjectsCount -> 
    				$"Currently maintained projects count %i{maintainedProjectsCount}"
    | Tester unitTestsPerHour -> 
    				$"My testing speed is %f{unitTestsPerHour} unit tests per hour"

In C# it can be rewritten like this

string GetWorkerInfo(Worker w)
{
    return worker switch
           {
               Worker.Developer(var knownLanguages) =>
                   $"Known languages {string.Join(',', knownLanguages)}",
               
               Worker.Manager(var maintainedProjectsCount) =>
                   $"Currently maintained projects count {maintainedProjectsCount}",
               
               Worker.Tester(var unitTestsPerHour) =>
                   $"My testing speed is {unitTestsPerHour} unit tests per hour",
               
               _ =>
                   throw new ArgumentOutOfRangeException(nameof(worker), worker, null)
           };
}

IDE hints become available to us (Rider still swears due to the lack of a default condition)

Comparison of implementations

C#

F#

Finding Available Options

IDE (Variants – classes-fields of the base class)

Tags (Enum)

Implemented Interfaces

IEquatable

IEquatable

IStructuralEquatable

Creating new objects

Constructor

Static method (New*)

Type definition in runtime

Reflection only

Properties for each option (Is*)

Generated Properties

Get/Set

Get-only

Generated Comparison Methods

==, !=, Equals

Equals

Recursive definition of Discriminated Unions

Yes, make the choice abstract

No, define another DU above and make it the choice in the current one

Representation in IL

Base abstract class with implementation options that inherit it

Data storage for each option

Properties with a backing field

Field deconstruction

There is

Notes:

conclusions

My version based on records is very similar to the one generated by the F# compiler (even superior in some ways).

There are many implementation options: on ordinary classes, on structures, partial classes.

Also, the advantage of the class implementation is the ability to define common fields – in Discriminated Unions, only the Tag and Is* properties are common to determine the subtype.

If anyone is interested in how Discriminated Unions are arranged in more detail, then there is a post on this topic.

That’s all for me. If I missed important points, please correct me.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *