The main goal of this snippet is to demonstrate a possible scenario of reducing an IEnumerable<Option<T>>
into an Option<T>
in C#. For better readability I give a basic introduction to Option<T>
and briefly cover how does it compare to the built-in Nullable<T>
.
Option<T>
is a type defined in the language-ext library for representing a value of type T
which might not be present. It's similar to the built-in Nullable<T>
, but Nullable<T>
doesn't force the developer to check for null references which might lead to unexpected runtime exceptions -- unless nullable context starting from C# 8 is enabled in the .csproj file
:
<PropertyGroup>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
Therefore, one simple way to think about Option<T>
is that it's a safer replacement for Nullable<T>
. In reality there is more to it than that: Option<T>
is a monad and consequently also a functor -- but we are not going into the details here. The only thing worthy of note is how can you "extract" the "contained" value given a variable option
holding an Option<T>
, let's say an Option<int>
. You can either match upon the option or use the convienence method IfNone
. The following two are equivalent, i.e. a == b
should be true
.
int a = option.Match(
Some: x => x,
None: () => -1
);
int b = option.IfNone(-1);
The example above uses the identity function in the Some
argument of Match
to extract the contained value as it is, but since Some
expects a Func<T, R>
(in this case a Func<int, R>
), it is also possible to transform the contained value.
Run
dotnet test --filter "FullyQualifiedName~BasicOptionTests"
and have a look into BasicOptionTests.cs
for reproducible usage examples.
Given two Option<T>
, can we define a sort of "merge" operation with function signature (Option<T>, Option<T>) -> Option<T>
? Let's say we want the merged result
- to be
None
if and only if both options areNone
and - be equal to one of the
Some
values otherwise,
then we can come up with the following implementation:
static Option<T> Merge<T>(this Option<T> a, Option<T> b) => a.IsSome ? a : b;
Run
dotnet test --filter "FullyQualifiedName~OptionCombiningTests.CanMergeTwoOptions"
and have a look into OptionCombiningTests.cs
for reproducible usage examples. Obviously, the usefulness of defining the "merge" operation this way largely depends on the context and it can easily happen that a different implementation with the same function signature is favourable. Please also note that this implementation is not commutative, as shown by
dotnet test --filter "FullyQualifiedName~OptionCombiningTests.MergeIsNotCommutative"
Now that we can merge two options, we can merge arbitrary many by repeatedly applying the binary merge:
static Option<T> Merge<T>(this IEnumerable<Option<T>> options) =>
options.Reduce((x, y) => x.Merge(y));
This can be seen in action by running
dotnet test --filter "FullyQualifiedName~OptionCombiningTests.CanMergeMoreOptions"
So how is it better than using Nullable<T>
as the container, where we could define our merge function just as easily:
public static Nullable<T> Merge<T>(this Nullable<T> a, Nullable<T> b)
where T: struct => a.HasValue ? a : b;
As mentioned earlier starting from C# 8 there is not much difference in terms of null-safety, but we'd loose all the nice monadic properties an Option<T>
has. We refer to this excellent blog post for a more detailed explanation.