a lightweight & fluent Option/Optional/Maybe Implementation for .Net & Mono
☝️ Supporting .net-standard 2.0 and 2.1
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 you will receive a Some-Optional.
If null is not the only decision criterion, ToOptional()
or Optional.From()
can be called with a predicate. Based 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 the easiest 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 additionally 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 = "Paul".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 = "Paul".ToSome();
var displayName = optional.Match(
some: name => v.ToUpperCase(),
none: () => "unknown PERSON"
)
Beside Match()
there are provided more specific methods called IfSome()
and IfNone()
. 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 of course 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 doesn't 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 single None-Optional is joined.
Fluent Optionals let you join up to seven Optionals, and beside Match()
, these composite Optionals also provide IfSome()
, IfNone()
, IsSome
and IsNone
.
-
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.
David Leitner |
Andreas Mewald |
Thomas Eizinger |
Paul Rohorzka |
Many thanks to all the others 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. (: