If you know F# (or Haskell, OCaml, …), you know the Option type (or Maybe type). If you’re not familiar with the Option type, you might want to have a look at the fantastic introduction by Scott Wlaschin.

If you know the Option type, you might be missing it when you’re programming in C# … I do. But I’m not the only one. There are multiple Option type implementations for C# on GitHub. The most popular implementation is probably language-ext, but it’s more than just an Option type (as the name implies).

With this blog post, my goal is to make you miss the Option type - even if you didn’t know the Option type before.

The Option Type

Let’s jump right into it and have a look at an implementation of the Option type:

public struct Option<T>
{
    public static Option<T> None => default;
    public static Option<T> Some(T value) => new Option<T>(value);

    readonly bool isSome;
    readonly T value;

    Option(T value)
    {
        this.value = value;
        isSome = this.value is { };
    }

    public bool IsSome(out T value)
    {
        value = this.value;
        return isSome;
    }
}

So, an Option can either be None or Some. If the Option is None, it doesn’t have an associated value. If the Option is Some, it does have an associated value. Basically, Option makes the absence of a value explicit.


Remark to Some(null): I don’t allow Some(null). If you pass null anyway, I treat it as None. I think that’s the preferable approach for most practical applications. However, that can be an issue if null has a meaning in your code base - the monad laws might not hold. But I won’t go into more details in this post.


If you want to know if an Option is None or Some, you have to call IsSome. That way, you can’t accidentally access the value of None.

if (option.IsSome(out var value))
{
    //option is Some(value)
}
else
{
    //option is None
}

So … no more NullReferenceExceptions?
Yup!
That’s great … but, isn’t there something called nullable reference types?

Prior to C# 8.0, the avoidance of NullReferenceExceptions was a major selling point for the Option type. But that’s not true anymore. The C# team chose nullable reference types over Options. In that spirit, if all you want is to avoid NullReferenceExceptions, don’t use Options, use nullable reference types instead.

But we can actually use nullable reference types to improve the Option implementation:

public struct Option<T> where T : notnull
{
    public static Option<T> None => default;
    public static Option<T> Some(T value) => new Option<T>(value);

    readonly bool isSome;
    readonly T value;

    Option(T value)
    {
        this.value = value;
        isSome = this.value is { };
    }

    public bool IsSome([MaybeNullWhen(false)]out T value)
    {
        value = this.value;
        return isSome;
    }
}

With the added type constraint T : notnull, it’s made explicit that I don’t allow null - before, it was only a convention. With the attribute MaybeNullWhen(false), you get a compiler warning if you use the out parameter without checking it for null when IsSome returns false.

There’s More to the Option Type

The Option type isn’t just a “nicer” null. But to gain its full power, it needs some more methods - I’ll show to you some of the most important. I put all the following methods in a static class called OptionExtensions. I omit the class definition for all but the first method.

Let’s start with Match:

public static class OptionExtensions
{
    public static U Match<T, U>(this Option<T> option,
        Func<T, U> onIsSome, Func<U> onIsNone) where T : notnull =>
            option.IsSome(out var value) ? onIsSome(value) : onIsNone();
}

Match is the preferred way to work with the value of an Option. You have to provide two Func. onIsSome is called if option is Some, onIsNone is called if option is None. Both have to return the same type. As you can see, Match returns the result of the called Func.

Match is preferred to IsSome because you can’t miss a case. You always have to provide onIsSome and onIsNone. IsSome, however, might feel more natural to use if you don’t have a value to return but want to do some side effects.

Next, we have Bind and Map:

public static Option<U> Bind<T, U>(this Option<T> option, Func<T, Option<U>> binder)
    where T : notnull where U : notnull =>
        option.Match(
            onIsSome: binder,
            onIsNone: () => Option<U>.None);

public static Option<U> Map<T, U>(this Option<T> option, Func<T, U> mapper)
    where T : notnull where U : notnull =>
        option.Bind(
            value => Option<U>.Some(mapper(value)));

Both methods transform an Option to another Option.

Let’s start with Bind. If option is Some(value), Bind calls binder with the value of option as parameter and returns the result. The result of binder is itself an Option. If option is None, Bind returns None - binder doesn’t get called.

Map is similar to Bind - that’s why it actually calls Bind. But the result of mapper gets wrapped with an Option before it’s returned by Map.

I don’t go into more detail. It’ll make more sense once we have some examples.

There are two more methods I want to show to you. We need them for the following examples:

public static Option<T> Filter<T>(this Option<T> option, Predicate<T> predicate)
    where T : notnull =>
        option.Bind(
            value => predicate(value) ? option : Option<T>.None);

public static T DefaultValue<T>(this Option<T> option, T defaultValue)
    where T : notnull =>
        option.Match(
            onIsSome: value => value,
            onIsNone: () => defaultValue);

Without much explanation:
Filter checks if the value of option satisfies predicate. If it does, option is returned unchanged. Else, Filter returns None.
DefaultValue returns the value of option if it’s Some. Else, it returns defaultValue.

I guess it’s time for some examples.

The Power of Bind

I only touch the surface here. If you want to know more about The Power of Composition or Railway Oriented Programming, check out the wonderful site F# for fun and profit by Scott Wlaschin.

Do you have code where the output of the first method is the input to the second method? And the output of the second method is the input to the third method. And … so on. Do your methods possibly return null? Then, your code might look something like this:

var result1 = Step1();
if (result1 != null)
{
    var result2 = Step2(result1);
    if (result2 != null)
    {
        var result3 = Step3(result2);
        //and so on ...
    }
}

A lot of repetitive boiler plate code, don’t you think? What if your methods returned an Option instead of something that might be null? Then, your methods would look like this:

static Option<ResultStep1> Step1()
{ /* return Some(value) or None */ }

static Option<ResultStep2> Step2(ResultStep1 input)
{ /* return Some(value) or None */ }

static Option<ResultStep3> Step3(ResultStep2 input)
{ /* return Some(value) or None */ }

Now, it’s time to unleash the awesome power of Bind! Let’s see how we can get rid of all that boiler plate code:

var result =
    Step1()
    .Bind(Step2)
    .Bind(Step3);

I don’t know about you, but I love how easy it is to stitch together methods using Bind. The first method that returns None stops the execution and None becomes the final result. Just like “short-circuiting” with conditional logical operators in C#.

The Missing LINQ

There’s another area where I like to use Options: data access. Let’s say we have the following repository:

interface Flights
{
    IEnumerable<Flight> GetFlights(string airline);
    Flight? GetFlight(string number);
}

Now, I want to get all the free seats of an airline where the departure time is between one and five hours in the future:

static bool CheckDepartureTime(Flight flight)
{
    var now = DateTime.Now;
    return flight.Departure >= now.AddHours(1) && flight.Departure <= now.AddHours(5);
}

static int GetFreeSeatsOfAirline(Flights flights, string airline) =>
    flights.GetFlights(airline)
    .Where(CheckDepartureTime)
    .Sum(_ => _.FreeSeats);

Ok, GetFreeSeatsOfAirline is compact and straight forward. Now, let’s suppose we want to get the free seats of a specific flight, but only if the flight satisfies CheckDepartureTime (don’t think about the usefulness of that constraint too much):

static int GetFreeSeatsOfFlight(Flights flights, string number)
{
    var flight = flights.GetFlight(number);
    return flight != null && CheckDepartureTime(flight) ? flight.FreeSeats : 0;
}

GetFreeSeatsOfFlight does pretty much the same as GetFreeSeatsOfAirline. Yet, it looks quite different. That bothers me. If two pieces of code do pretty much the same, I expect them to look pretty much the same.

Let’s change the repository method GetFlight to return Option<Flight> instead of Flight?:

interface Flights
{
    IEnumerable<Flight> GetFlights(string airline);
    Option<Flight> GetFlight(string number);
}

Now, we can implement GetFreeSeatsOfFlight like this (I included GetFreeSeatsOfAirline for comparison):

static int GetFreeSeatsOfFlight(Flights flights, string number) =>
    flights.GetFlight(number)
    .Filter(CheckDepartureTime)
    .Map(_ => _.FreeSeats).DefaultValue(0);

static int GetFreeSeatsOfAirline(Flights flights, string airline) =>
    flights.GetFlights(airline)
    .Where(CheckDepartureTime)
    .Sum(_ => _.FreeSeats);

I’d say the two methods look pretty much the same. And that comes with a big advantage: Because they do pretty much the same, chances are high that if you have to change one of the methods, you also have to change the other method. Since their structure is similar, it’s easy to apply the same change to both methods. Basically, if you understand one method, you automatically understand the other method.

I sometimes describe the Option type as “LINQ for single values”. There are scenarios where LINQ doesn’t feel complete without the Option type.

Implementation

You can find an Option type implementation in my GitHub repository functional-extensions.


Have I achieved my goal? Do you miss the Option type in C#? Or is it some functional shenanigans nobody needs in an object-oriented language?