Table of Contents

Should I use Maybe<T>?

If you've already decided Maybe<T> is for you, have a look at the Using Maybe<T> article.

If not, here are some things to think about before making a choice:

Pros & Cons

Maybe<T> gives you a way to care less when nulls occur, and can help to resolve Code Analysis warning CA1062. Like Nullable<T>, it can contain a value or a null and gives you safe ways of determining which it is. However, unlike Nullable<T>, you don't necessarily need to unpack the contents of a Maybe<T> in order to work with it. Methods are available to allow you to act on it without evaluating it first.

Methods can operate on and return Maybes rather than failing, and methods that do that can be chained together in series safely without worrying about a failure occurring somewhere at the start or middle of the chain. Methods that would previously have converted a T to a U can instead convert a Maybe<T> to a Maybe<U> - so the underlying type changes - all without having to check whether a result actually exists before it's needed somewhere at the end of the process.

Programmers familar with other languages may recognise Maybe<T> as being similar to features in their own languages. The name "Maybe" comes from Haskell, and Java programmers may be accustomed to using Optional<T>.

Comparison with Nullable<T>

As mentioned above, Maybe<T> gives you something that Nullable<T> doesn't - the ability to not only pass a result around without explicitly evaluating it, but to act on it and perform transformations on it without explicitly evaluating it. It can make it safer to work with nulls, because explicit checks for null are far fewer and may not be needed at all. And the Maybe creation method Maybe.WithValue<T>(T) will automatically convert a Nullable<T> into a Maybe<T>, whether or not you have nullable reference types enabled.

However, Nullable<T> has some significant advantages. It's built into the language, and it has support in analyzers that will help to ensure that you use it correctly and well; particularly if you enable the nullable reference types introduced in C# 8.0. Microsoft have provided some advice on strategies for enabling nullable reference types.

Nullable<T>, with nullable reference types, can give you a well-checked strategy for dealing with nulls. Maybe<T> has the potential to go further, but requires a significant commitment to really get the best out of it.

If you're ready to jump into Maybe<T>, then Using Maybe<T> is the best place to start.


Comparison with other languages

Maybe<T> has broad feature parity with Java's Optional<T> (and more) but features don't necessarily have the same names. For all the features of Maybe<T>, refer to Maybe<T>, Maybe, and MaybeExtensions in the API documentation. Here are Maybe<T>'s equivalents to features in Optional<T>:

Modifier Returns Optional<T> member Returns Maybe<T> member
static Optional<T> empty() Maybe<T> Empty
static Optional<T> of(T value) Maybe<T> WithKnownValue<T>(T inValue)
static Optional<T> ofNullable(T value) Maybe<T> WithValue<T>(T inValue)
boolean equals(Object obj) bool Equals(object obj)
Optional<T> filter(Predicate<? super T> predicate) Maybe<T> Where(Func<T, bool> inPredicate)
<U> Optional<U> flatMap(Function<? super T, ? extends Optional<? extends U>> mapper) Maybe<TResult> Apply<TResult>(Func<T, Maybe<TResult>> inConvert)
T get() T GetValueOrThrow(string inErrorMessage)
int hashCode() int GetHashCode()
void ifPresent(Consumer<? super T> action) void IfExists(Action<T> inSome)
void ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction) void DoEither(Action<T> inSome, Action inNone)
boolean isEmpty() bool Exists (negated)
boolean isPresent() bool Exists
<U> Optional<U> map(Function<? super T, ? extends U> mapper) Maybe<TResult> Apply<TResult>(Func<T, TResult> inConvert)
Optional<T> or(Supplier<? extends Optional<? extends T>> mapper) Maybe<T> GetValueOrMaybe(Func<Maybe<T>> inAlternativeValueFactory)
T orElse(T other) T GetValueOr(T inDefaultValue)
T orElseGet(Supplier<? extends T> supplier) T GetValueOr(Func<T> inDefaultValueFactory)
T orElseThrow() T GetValueOrThrow(string inErrorMessage)
<X extends throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X extends Throwable T GetValueOrThrow(string inErrorMessage) Not an exaact equivalent.
Stream<T> stream()
String toString() string ToString()