Option
The Option type is a powerful construct used to represent the presence or absence of a value. This type is particularly useful for eliminating null reference errors, a common source of bugs in C# applications.
An Option can be either Some (containing a value), or None (indicating the absence of a value). By using Option, developers can enforce safer code practices, making it explicit when a variable, property, or parameter might not have a value and requires handling both cases.
For error handling, you can use the Result type, which can encapsulate the error information.
Overview
The Option type is a container for a value, so it can be treated as a collection with a single value supporting operations such as:
- Mapping: apply transformations to the value within a
Option - Filtering: filters a value based on a predicate
- Flattening: apply a function that returns an
Optionand flattens the result
Usage
The following example demonstrates how to use Option to handle non-whitespace strings:
public Option<string> OnlyNonWhitespace(string? value) =>
string.IsNullOrWhiteSpace(value)
? None
: Some(value);In this example:
- The function
OnlyNonWhitespacetakes an input string that may be null or contain whitespace. - If the input string is null, empty or contains only whitespace, the function returns
None, indicating the absence of a valid string. - If the input string is non-whitespace, the function returns
Somewith the actual string value.
Accessors and Unwrapping
Functions that are used to access or extract values from their containers.
TryGet / Unwrap
You can use TryGet as an escape hatch to get the value out.
Option<string> nonWhitespace = OnlyNonWhitespace("hello");
if (nonWhitespace.TryGet(out var value))
{
Console.WriteLine(value);
}
// Alternatively, `Unwrap` can be used,
// but will throw an exception if in the `None` state.
string value = nonWhitespace.Unwrap();Or you can use UnwrapOr / UnwrapOrElse and specify a fallback value for when option is empty.
Option<string> nonWhitespace = OnlyNonWhitespace("hello");
string value = nonWhitespace.UnwrapOr("<undefined>");ToNullable
The ToNullable / ToNullableValue is used to unwrap the value from option and fallback to null when it is empty.
Option<string> nonWhitespace = OnlyNonWhitespace("hello");
string? value = nonWhitespace.ToNullable();Pattern Matching and Transformation
Functions that are used to apply transformations or perform pattern matching on values within containers.
Match
To handle the possible states of the option:
int length = OnlyNonWhitespace(" hello ")
.Map(x => x.Trim())
.Match(
Some: x => x.Length,
None: () => 0);There are shortcuts for a lot of these - for example, the above could be written as:
int length = OnlyNonWhitespace(" hello ")
.Map(x => x.Trim())
.Map(x => x.Length)
.UnwrapOr(0);OkOr / OkOrElse
You can turn Options into other types, such as Result:
Result<int, string> result = OnlyNonWhitespace(" hello ")
.Map(x => x.Trim())
.Where(x => x.Length > 0)
.OkOr("That string was only whitespace! Bad!")ValidOr / ValidOrElse
To convert an Option to Validation:
Validation<int, string> result = Some("Hello")
.Where(x => x.Length < 10)
.ValidOr("Too long text");Filtering and Conditional Operators
Functions that are used to filter or conditionally manipulate values within containers.
Where
Filter the element of an option, if any, based on a predicate.
Option<int> nonZeroLength = OnlyNonWhitespace(" hello ")
.Map(x => x.Trim())
.Map(x => x.Length)
.Where(x => x.Length > 0);OfType
Use OfType to filter the value held in Some based on its type.
This is essentially a shorthand for
FlatMap(a => a is U u ? Some(u) : None)
Mapping and Flat Mapping
Functions that are used to apply transformations to each element within a container and manage nested containers.
Map / Select
Transforming the value:
OnlyNonWhitespace(" hello ")
.Map(x => x.Trim());LINQ syntax is supported too.
Option<int> result =
from a in Some(3)
select a + 1;FlatMap / SelectMany
Monadic bind (also called flat mapping):
Option<string> greeting = OnlyNonWhitespace(" hello ")
.Map(x => x.Trim())
.FlatMap(a =>
OnlyNonWhitespace(" world ")
.Map(y => y.Trim())
.Map(b => $"{a} {b}");That wasn't very pretty - you can use LINQ to make it nicer:
Option<string> greeting =
from a in OnlyNonWhitespace(" hello ").Map(x => x.Trim())
from b in OnlyNonWhitespace(" world ").Map(y => y.Trim())
select $"{a} {b}";Do
Use Do to execute an imperative operation when the option has a value.
Some("Hello")
.Do(x => Console.WriteLine(x));Aggregation and Collection Operations
Functions that are used to aggregate or collect values from multiple containers.
Somes
Use the Somes to extract the values from a sequence of options.
IEnumerable<int> numbers = ListOf
.Many(Some(4), Some(3), None, Some(10), None, None, Some(2))
.AsEnumerable()
.Somes()SomesMap
You can use SomesMap as a shortcut for Somes + Map.
IEnumerable<int> lengths = ListOf
.Many(Some("Hello"), Some("world"))
.SomesMap(v => v.Length)Traverse
You can traverse between various other container types. For example:
IReadOnlyList<int> list = [2, 4, 6];
Option<IReadOnlyList<int>> listOfOnlyEvenNumbers =
list.Traverse(x => x % 2 == 0 ? Some(x) : None);
// Some([2, 4, 6])Sequence
Use Sequence to traverse without the mapping step.
This is equivalent to
Traverse(Identity)/Traverse(x => x)
IReadOnlyList<Option<int>> list = [Some(2), Some(4), None, Some(6)];
Option<IReadOnlyList<int>> sequenced =
list.Sequence();TryAggregate
Use TryAggregate to attempt an aggregation of a sequence with a custom function that can short-circuit if any step fails.
var numbers = new[] { 1, 2, 3, 4 };
var result = numbers.TryAggregate(
seed: 0,
func: (acc, item) => item > 0 ? Some(acc + item) : None
);
// Some(10)
var invalidResult = numbers.TryAggregate(
seed: 0,
func: (acc, item) => item < 2 ? Some(acc + item) : None
);
// NonePrelude
The Prelude class provides the following functions for Option:
Some / None
Returns a wrapped value or an empty Option.
public Option<string> OnlyNonWhitespace(string? value) =>
string.IsNullOrWhiteSpace(value)
? None
: Some(value);Optional
Use Optional to convert a nullable value to Option. If the input value is not null, the result will be Some(value), otherwise the result will be an empty option (None).
string? value = "Hello";
Option<string> wrapped = Optional(value);