a lightweight & fluent Option/Optional/Maybe Implementation for .Net & Mono
PM> Install-Package FluentOptionals
Usually this is the place to go into detail about Optionals and why you should use them. I won't do this as it is already done better than I ever could:
- you don't know what's an Optional/Option/Maybe - read this
- you know what it is, but you are not sure to use it in your project - read this
- you like to use them in your C# project - download Fluent Optionals
Basically there are two types of optinonals, those who have a value (we will call them Some-Optionals) and those who have no value (we will call them None-Optionals). Creating them is easy:
var some = Optional.Some(20);
var none = Optional.None<int>();
Through the existence of Null-References in C#, a common use case is to create Some or None-Optionals based on a Null-Check. That's why there's a shortcut for this:
//traditional way to create Optionals
var x = (value != null) ? Optional.Some(value) : Optional.None<int>();
//shortcut 1
var y = value.ToOptional();
//shortcut 2
var z = Optional.From(value)
Those three approaches will always produce the same result. If the given value is null a None-Optional is returned, in all other cases a Some-Optional.
If null is not the only decision criterion, ToOptional()
or Optional.From()
can be called with a predicate. Base on this a Some or None-Optional is created.
var value = "david";
var some = value.ToOptional(v => v == "david");
var none = value.ToOptional(v => v == "thomas");
//or
Optional.From(value, v => v == "david");
Consider if ToOptional()
is called on null, it will always return a None-Optional without even evaluating the predicate (to avoid Null-Reference-Exceptions).
If you want to receive a Some or None-Optional explicit, you can use value.ToSome()
or ToNone()
:
10.ToSome() //always produces a Some-Optional
//except the value is none, this causes a 'SomeCreationWithNullException'
10.ToNone() //ignores the value and always produces a None-Optional
When retrieving values, Optionals force you to always consider Some and None (if a value is present or not).
ValueOr()
is one approach to retrieve an Optional's value.
"Max".ToSome().ValueOr("unknown name"); //returns "Max"
"Max".ToNone().ValueOr("unknown name"); //returns "unknown name"
"Max".ToNone().ValueOr(() => nameServer.GetDefaultName()); //will evaluate value lazy
"Max".ToNone().ValueOrThrow(new Exception("name was not provided")); //throws exception
To verify if an Optional is Some or None the properties IsSome
and IsNone
are provided.
Another way to get an Optional's value is to use Match()
. Inspired by pattern matching it provides a nice way to handle Some and None-Optionals.
var optional = "Max".ToSome();
optional.Match(
some: name => Console.WriteLine("hey, " + name),
none: () => Console.WriteLine("we don't know your name")
)
Match
can also return a value:
var optional = "Max".ToSome();
var displayName = optional.Match(
some: name => v.ToUpperCase(),
none: () => "unknown PERSON"
)
Beside Match()
there are provided more specific methods called MatchSome()
and MatchNone()
. They only take one action.
Map()
can transform the value of an Optional. If the Optional is None the transformation won't be applied and the new Optional stays None. If the give map function returns null, the Optional becomes None.
10.ToOptional().Map(i => i * 2) //returns Some-Optional<int> -> 20
10.ToNone().Map(i => i * 2) //returns None-Optional<int>
"test".ToOptional().Map(i => null) //returns None-Optional<int>
Map()
can also change the Optional's type.
Optional<string> result = 10.ToOptional().Map(i => i.ToString()) //returns Some-Optional<String> -> "10"
Optional<string> result = 10.ToNone().Map(i => i.ToString()) //returns None-Optional<String>
Optional<string> result = Optional.None<int>().Map(i => null) //return None-Optional<String>
Shift()
provides a possibility to change a Some to a None-Optional. If Shift()
is called on a None-Optional, it does not alter anything.
var transformed = 10.ToOptional().Shift(v => v < 100); //returns None-Optional<int>
var transformed = 200.ToOptional().Shift(v => v < 100); //returns Some-Optional<int> -> 200
Joining Optionals can be achieved by using Join()
.
Optional
.From(_priceWebService.GetProductPrice(productId))
.Join(_reductionWebService.GetProductReduction(productId))
.Join(_taxesWebService.GetProductTaxes(productId, country))
.Match(
some: (price, reduction, taxes) => $"{price - reduction} (excl. {taxes}% taxes)",
none: () => $"price is currently not available"
)
A joined Optional evaluates to none as soon as a None-Optional gets joined.
Fluent Optionals let you join up to 7 Optionals, and beside Match()
also MatchSome()
, MatchNone()
, IsSome
and IsNone
can be called.
-
ToOptionalList()
: transforms aIEnumerable
to aIEnumerable
of Optionals.IEnumerable<Optional<int>> OptionalList = new List<int>{ 1, 2, 3 }.ToOptionalList(); new List<string>{ null, "test", null }.ToOptionalList(); //returns a List of 3 Optionals: [None, Some, None] new List<int>{ 0, 1, 3, 0 }.ToOptionalList(v => v > 0); //returns a List of 4 Optionals: [None, Some, Some, None]
-
FirstOrNone()
: returns the first Some-Optional. If there is no first element, it returns a None-Optional.. -
LastOrNone()
: returns the last Some-Optional. If there is no last element, it returns a None-Optional. -
SingleOrNone()
: if exactly one Some-Optional exists, this is returned. I all other cases it returns a None-Optional.
Many thanks to paulroho and thomaseizinger for providing inspiration and feedback.
And of course to Tony Hoare, if he won't have made his one billion dollar mistake, this library would be absolutely useless. (: