Discriminated unions in C#

Discriminated unions are an expressive construct used to describe values that can be modeled under one or more cases. This is the syntax as in MSDN documentation:

type type-name =
   | case-identifier1 [of type1 [ * type2 ...]
   | case-identifier2 [of type3 [ * type4 ...]

Discriminated unions describe also F# lists and when recursive they can model tree data structure.

Let’s start with an example that models the booking in a vacation facility:

type Booking =
    | Free
    | Single of DateTime
    | Range of DateTime * DateTime

This type can be used to define following values:

let free = Free
let single = Single(DateTime(2013,04,23))
let range = Range(DateTime(2013,04,23), DateTime(2013,04,26))

Main advantage of working with discriminated unions is that we can decompose them using pattern matching:

let describeBooking b =
    match b with
        | Single day -> "vacation on " + day.ToShortDateString()
        | Range (startDay, endDay)  -> "from " + startDay.ToShortDateString() + " to " + endDay.ToShortDateString()
        | _ -> "no vacation"

The function describeBooking matches against a Booking type and returns a descriptive string.

Obviously we only scratched the surface on the subject, so I invite you to read F# documentation or this wiki book.

Discriminated Unions in CSharp

Finally we get to the core of the post, where we I’ll follow the tecnique described in Real World Functional Programming to define discriminated unions in C#.

As you can imagine the OO construct we’ll use is inheritance.

As first we need define an enumeration with all cases:

enum BookingType { Free, Single, Range }

Then an abstract class that accepts the discriminator in the constructor and expose it publicly:

abstract class Booking
  private readonly BookingType tag;<br/>
  protected Booking(BookingType tag)
    this.tag = tag;
  public BookingType Tag
    get { return this.tag; }

Now we define one sealed derived class for each case:

sealed class Free : Booking { public Free() : base(BookingType.Free) }

sealed class Single : Booking
  private readonly DateTime date;<br/>
  public Single(DateTime date)
    : base(BookingType.Single)
    this.date = date;
  public DateTime Date
     get { return this.date; }

sealed class Range : Booking
  private readonly DateTime fromDate;
  private readonly DateTime toDate;<br/>
  public Single(DateTime fromDate, DateTime toDate)
    : base(BookingType.Range)
    this.fromDate = fromDate;
    this.toDate = toDate;
  public DateTime FromDate
     get { return this.fromDate; }
  public DateTime ToDate
     get { return this.toDate; }

Since C# lacks match constuct we can use switch against Tag discriminator and cast specific derivates:

string describeBooking(Booking booking)
  switch (booking.Tag)
    case Booking.Single:
      return "vacation on " + ((Single)booking).Date.ToShortDateString();
    case Booking.Range:
      var range = (Range)booking;
      return "from " + range.FromDate.ToShortDateString() + " to "
        + range.ToDate.ToShortDateString()
      return "no vacation";

It’s clear that this is a particular use of inheritance intended to mimic discriminated unions. Here the lack of extensibility is by design.

I suggest you such construct when the number of cases is predefined or will change little over the time.

I’ll report the same BCL usage of this pattern as in the book quoted above, System.Linq.Expression. In this type the desciminator is named NodeType and is defined as ExpressionType enumeration.

This post is part of YES! to NOOO initiative.

Written on April 23, 2013