Show / Hide Table of Contents

Option

The Option<T> is a structure that encapsulates the concept of having a value or not. It safely handles this mechanic without you have to worry about having a null value somewhere. It does this for you. What you just have to do to use it is to put a using like this for basic implementation:

using Here;

And this one for extensions:

using Here.Extensions;

Why Option?

Because it offers a safe handling of null values. It is also a nice alternative to Nullable that only works with value type while Option handle every types.


Create an option

You can create an Option<T> from any type via the extension ToOption() or the "constructor". See examples below:

Option<string> optionStr = Option<string>.Some("My string");
Option<string> optionStr2 = "My string2".ToOption();
Option<string> optionStr3 = "My string2";     // Implicit conversion
Option<string> optionStr4 = Option.From("My string2");

// Empty option
Option<string> emptyOptionStr = Option<string>.None;
Option<string> emptyOptionStr2 = Option.None;  // Implicit creation
Option<string> emptyOptionStr3 = Option.From(null);

It is also possible to use implicit conversion that will fit for calls to API code that your not the owner or simply code you don't want to update.

int GetAnInteger()
{
    return 42;
}

// Implicit creation
Option<int> optionInt = GetAnInteger();

Working with Option

When you use an Option you can access to its value if it has one via the Value property. You can check before that it has one via the HasValue and/or HasNoValue properties, or directly by checking the Option variable as it supports the true and false operators. But you can also simply use extensions that will really help you to keep a clean and functional code.

Option<int> optionInt = 42.ToOption();
if (optionInt.HasValue)
    Console.WriteLine(optionInt.Value);

// Equivalent to
if (optionInt)
    Console.WriteLine(optionInt.Value);

Perform a treatment

// Empty string
string emptyString = null;

emptyString.ToOption().If(str =>
{
    // Here 'str' is guaranteed to be not null. In this case this statement will never be called.
    Console.WriteLine(str);
});

// Not empty string
"Hello world!".ToOption().If(str =>
{
    // In this case it will output "Hello world!".
    Console.WriteLine(str);
});

Here is an example with the If (has value), but there is also the IfElse that allows you to handle the case the Option has no value, and many others.

Unwrapping value

Extensions

After having wrapped a value you can also safely unwrap it. For this you have multiple methods, for example by using the Or operator. You will be able unwrap with a default value or a factory method like:

// With a wrapped value
Option<int> optionInt = 42.ToOption();

int unwrappedValue = optionInt.Or(12);        // 42
int unwrappedValue2 = optionInt.Or(() => 12); // 42
int unwrappedValue3 = optionInt.OrDefault();  // 42
int unwrappedValue3 = optionInt.Unwrap();     // 42

// With no wrapped value
Option<int> emptyOptionInt = Option.None;

int unwrappedValue = emptyOptionInt.Or(12);        // 12
int unwrappedValue2 = emptyOptionInt.Or(() => 42); // 42
int unwrappedValue3 = emptyOptionInt.OrDefault();  // 0

You can also consider the case when the option is 'None' as an error and use the OrThrows:

Option<int> emptyOptionInt = Option.None;

int unwrappedValue = emptyOptionInt.OrThrows(new InvalidOperationException()); // Throws

Comparisons

It is also possible to perform equality comparison directly on the wrapped value through the Option like shown below:

Option<int> optionInt = Option<int>.Some(12);
if (optionInt == 12)
{
    // Do something...
}

// Or with reference types
var testClass = new TestClass();
Option<TestClass> optionClass = Option<TestClass>.Some(new TestClass());
if (optionClass == testClass)
{
    // Do something...
}

Cast value

You can cast the value wrapped by the Option in a safe way by using a converter method or an 'as' cast like following:

// For basic types
Option<int> optionInt = 42.ToOption();
Option<float> optionFloat = optionInt.Cast(intValue => (float)intValue);

// For reference types
// Let assume that we have a type TestClass and a type SubTestClass that inherits from TestClass
var testObject = new SubTestClass()
Option<TestClass> optionTestClass = Option<TestClass>.Some(testObject);
Option<SubTestClass> optionSubTestClass = optionTestClass.Cast<SubTestClass>();

Enumerable extensions

Basic methods

Methods like FirstOrDefault() and LastOrDefault have their Option equivalent that are FirstOrNone and LastOrNone which respectively return an Option with the first or last enumerable element and None if there is not any.

var listInts = new List<int> { 1, 2, 3 };

Option<int> optionInt = listInts.FirstOrNone(); // Option with value 1
Option<int> optionInt2 = listInts.FirstOrNone(intValue => intValue == 3); // Option with value 3
Option<int> optionInt3 = listInts.FirstOrNone(intValue => intValue == 4); // None Option

// This is the same for LastOrNone

Wrapped enumerables

There are also extensions that allow to perform treatments directly on a wrapped enumerable (or other derived collection).

For example you can use ForEach or Where like this, all these methods are suffixed by "Item" or "Items":

var listInts = new List<int> { 1, 2, 3 };

Option<IList<int>> optionListInts = listInts;
optionListInts.ForEachItems((int item) => Console.WriteLine(item));

IEnumerable<int> enumerableInts = optionListInts.WhereItems((int item) => item >= 2);	// 2 3

Enumerable to unwrapped values

If you have an enumerable of Option<T> then you may want to only keep data that really have a value.

For this you can simply extract values via the ExtractValues extension. Note that you can also generate a List, Dictionary in the same way.

IEnumerable<Option<float>> GetData()
{
    // Do something and yield results
}

IEnumerable<float> relevantValues = GetData().ExtractValues();
float[] relevantArray = GetData().ToArray();
List<float> relevantList = GetData().ToList();
Dictionary<string, float> relevantDictionary = GetData().ToDictionary((float val) => val.ToString());

Option enumerator

Option<T> is implementing the GetEnumerator method which allows to use it in a foreach statement.

This allows to run foreach content only if the Option<T> has a value.

foreach (int value in Option<int>.Some(42))
{
    Console.WriteLine($"Option has value {value}."); // Option has value 42.
}

foreach (int value in Option<int>.None)
{
    Console.WriteLine($"Option has value {value}."); // Not executed
}

Linq extensions

There is also a support of common Linq extensions like Any, All, Contains, Select, Where, ForEach and Aggregate.

See example below:

// Dummy examples
Option<int> optionInt = 12.ToOption();

if (optionInt.Any())
{
    // code
}

int result = optionInt.Aggregate(10, (initial, value) => initial + value); // 22

bool result = optionInt.Where(intValue => intValue > 10) // Is true
                       .Contains(12);                    // Is true

Option<Type> optionType = typeof(string);
optionType.Select(type => type.Name)
          .ForEach(name => Console.WriteLine(name));

Lookup and Parsing

Option also allows you to safely lookup in a dictionary or parsing of basic types from string. It provides an implementation that fit most common usages.

// Lookup in a dictionary
var dictionary = new Dictionary<int, string>
{
    [11] = "string 11",
    [12] = "string 12"
};

Option<string> optionString = dictionary.TryGetValue(11);   // string 11
Option<string> optionString2 = dictionary.TryGetValue(14);  // None option

// Parsing
Option<int> optionInt = "12".TryParseInt();   // 12
Option<int> optionInt2 = "1.5".TryParseInt(); // None Option
Option<float> optionFloat = "1.5".TryParseFloat(); // 1.5

Lookup and parsing are features that can be completed easily if you have a method that match the following delegates:

// For TryGet
public delegate bool TryGet<in TInput, TValue>([CanBeNull] TInput input, out TValue value);

// For TryParse
public delegate bool TryParse<TValue>([CanBeNull] string input, NumberStyles style, IFormatProvider culture, out TValue value);

The library provide a default implementation for them:

// Try Get
public static Option<TValue> Get<TInput, TValue>([CanBeNull] TInput input, [NotNull] TryGet<TInput, TValue> tryGetFunc)
{
    return tryGetFunc(input, out TValue result)
        ? result.ToOption()
        : Option.None;
}

// TryParse
public static Option<TValue> Parse<TValue>([CanBeNull] string input, [NotNull] TryParse<TValue> tryParseFunc, NumberStyles style, IFormatProvider culture)
{
    return tryParseFunc(input, NumberStyles.Any, culture, out TValue result)
        ? result.ToOption()
        : Option.None;
}

And use them for each basic type, but you can also create you own implementation in the same way.

Numeric Types casts

Each numeric type, which means byte, sbyte, short, ushort, int, uint, long, ulong, decimal, float and double have support of the conversion to another numeric type and also to bool.

This means that you can easily convert Option<TNumeric> to an Option<TOtherNumeric> or Option<bool>. Each Option<TNumeric> have extensions to do this. Here are some examples:

Option<double> optionDouble = Option<double>.Some(42.2d);

// Double to bool
Option<bool> optionBool = optionDouble.ToBool();   // True

// Double to int
Option<int> optionInt = optionDouble.ToInt();      // 42

// Etc.

// As a consequence you can chain calls like this:
string myString = "51.52";
Option<double> optionDouble = myString.TryParseInt().ToDouble();  // 51

Bridge to Result

It is possible to convert an Option<T> to a Result, Result<T>, CustomResult<TError> or Result<T, TError>.

For the first two conversions, both also support implicit conversion.

var optionInt = Option<int>.Some(42);

Result result = optionInt.ToResult();    // Explicit => Result.OK
Result result = optionInt;               // Implicit => Result.OK


var emptyOptionInt = Option<int>.None;

Result result = emptyOptionInt.ToResult();    // Explicit => Result.Fail
Result result = emptyOptionInt.ToResult("Custom failure message");    // Explicit => Result.Fail
Result result = emptyOptionInt;               // Implicit => Result.Fail

Bridge to Either

It is possible to convert an Option<T> to an Either<TLeft, T>.

Option<int> optionInt = Option<int>.Some(12);
Either<string, int> eitherStrInt = optionInt.ToEither("Error");         // eitherStrInt.IsRight

Option<int> emptyOptionInt = Option<int>.None;
Either<string, int> eitherStrInt = emptyOptionInt.ToEither("Error");    // eitherStrInt.IsLeft
  • Improve this Doc
In This Article
Back to top Here