Struct Optional<T>
Provides type-safe access to a nullable value.
Implements
Inherited Members
Namespace: Recore
Assembly: Recore.dll
Syntax
[JsonConverter(typeof(OptionalConverter))]
public struct Optional<T> : IEquatable<Optional<T>>
Type Parameters
| Name | Description |
|---|---|
| T |
Remarks
Optional<T> differs from Nullable<T> in the following ways:
- Nullable<T> has syntactic sugar like the alias
T?and the operators?.and?? - Nullable<T> has some special-case behavior in the CLR for boxing and GetType()
- Nullable<T> works only with value types while Optional<T> works with both value and reference types
- Nullable<T> provides direct access to its value through Value, while Optional<T> requires access through its methods
The last point is the most significant. Accessing the value directly through Value opens up the possibility for a NullReferenceException. With Optional<T>, once you have an optional value, all operations on it happen in an "optional context." You can't get rid of Optional<T> until you do something to handle the null case such as by calling Switch<TResult>(Func<T, TResult>, Func<TResult>) or ValueOr(T).
Examples
Here are a few examples for how to work with an optional value.
Optional<T> has public constructors that you can use, but the easiest way to create an instance is to use the helper methods:
Optional<string> opt; // Optional is a value type, so this defaults to empty
opt = "hello"; // Creates an Optional with the value "hello"
opt = Optional<string>.Empty; // Now it's empty again
Switch() is the main way to work with Optional.
It's akin to a switch statement.
opt.Switch(
x => Console.WriteLine("Message: " + x),
() => Console.WriteLine("No message"));
You can also return a value like a switch expression.
In this case, both legs of the Switch() must return the same type.
int messageLength = opt.Switch(
x => x.Length,
() => -1);
But, a more idiomatic way to handle the above case is to use OnValue().
This will map Optional<T> to Optional<U>.
If the original Optional<T> is empty, the new one will also be empty.
Optional<int> messageLength = opt.OnValue(x => x.Length);
We can work with the value procedurally, but this doesn't get us access to the value.
For that, we can use AssertValue().
It will try to retrieve the value and throw InvalidOperationException if there's no value.
if (opt.HasValue)
{
string value = opt.AssertValue();
}
A safer way to get the value out is to use ValueOr(), which requires you to pass a fallback value.
string message = opt.ValueOr(default);
There's a special case where OnValue() and Then() can cause their callback to work differently than it does outside the Optional context. Consider these two calls:
int? value = null;
new Optional<int>(value ?? 0)
new Optional<int?>(value).OnValue(x => x ?? 0)
Normally, you'd expect these two lines to be equivalent for any function.
But if value is null, they won't be the same.
The same thing goes for Then():
Optional<int> NullableToOptional(int? n) => Optional.Of(n ?? 0);
NullableToOptional(value)
new Optional<int?>(value).Then(NullableToOptional)
The problem is that "null coalescing" operations (by which I mean any function that turns null into a non-null value, including the ?? operator) won't work as expected when passed through Optional<T>.
This is because they will never receive null as an input.
(A note to functional-programming aficionados: the first example has implications for associativity.
Passing a composed function to OnValue() may produce a different result than two separate OnValue() calls if null coalescing is involved.
The second example is the monad law of left identity.)
Since any reference type can be null, there's no way to fix it except to avoid it.
Just know that you can't perform null-coalescing inside of an Optional context.
Rather, use Optional's own operations.
Every example I can think of stands out as a bug if you examine it under this principle.
For more information, this writeup explores the issue in the context of Java's optional type.
Constructors
| Improve this Doc View SourceOptional(T)
Creates an Optional<T> with a value.
Declaration
public Optional(T value)
Parameters
| Type | Name | Description |
|---|---|---|
| T | value |
Remarks
If null is passed for value, then the Optional<T>
is considered empty.
Properties
| Improve this Doc View SourceEmpty
Creates an Optional<T> without a value.
Declaration
public static Optional<T> Empty { get; }
Property Value
| Type | Description |
|---|---|
| Optional<T> |
Remarks
While an empty Optional<T> can also be created by calling the default constructor
or passing null to the constructor,
Empty is more expressive, making the absence of a value more obvious.
HasValue
Indicates whether the Optional<T> was created with a value.
Declaration
public bool HasValue { get; }
Property Value
| Type | Description |
|---|---|
| Boolean |
Methods
| Improve this Doc View SourceAssertValue()
Extracts the value or throws an InvalidOperationException if the Optional<T> is empty.
Declaration
public T AssertValue()
Returns
| Type | Description |
|---|---|
| T |
Equals(Optional<T>)
Determines whether this instance and another Optional<T> have the same values.
Declaration
public bool Equals(Optional<T> other)
Parameters
| Type | Name | Description |
|---|---|---|
| Optional<T> | other |
Returns
| Type | Description |
|---|---|
| Boolean |
Equals(Optional<T>, IEqualityComparer<T>)
Determines whether this instance and another Optional<T> have the same values using the given IEqualityComparer<T>.
Declaration
public bool Equals(Optional<T> other, IEqualityComparer<T> comparer)
Parameters
| Type | Name | Description |
|---|---|---|
| Optional<T> | other | |
| IEqualityComparer<T> | comparer |
Returns
| Type | Description |
|---|---|
| Boolean |
Equals(Object)
Determines whether this instance and another object, which must also be an Optional<T>, have the same value.
Declaration
public override bool Equals(object obj)
Parameters
| Type | Name | Description |
|---|---|---|
| Object | obj |
Returns
| Type | Description |
|---|---|
| Boolean |
Overrides
| Improve this Doc View SourceGetHashCode()
Returns the hash code for the underlying type or zero if there is no value.
Declaration
public override int GetHashCode()
Returns
| Type | Description |
|---|---|
| Int32 |
Overrides
| Improve this Doc View SourceIfValue(Action<T>)
Takes an action only if the Optional<T> has a value.
Declaration
public void IfValue(Action<T> onValue)
Parameters
| Type | Name | Description |
|---|---|---|
| Action<T> | onValue |
OnValue<TResult>(Func<T, TResult>)
Maps a function over the Optional<T>'s value, or propagates Empty.
Declaration
public Optional<TResult> OnValue<TResult>(Func<T, TResult> f)
Parameters
| Type | Name | Description |
|---|---|---|
| Func<T, TResult> | f |
Returns
| Type | Description |
|---|---|
| Optional<TResult> |
Type Parameters
| Name | Description |
|---|---|
| TResult |
Switch(Action<T>, Action)
Chooses an action to take depending on whether the Optional<T> has a value.
Declaration
public void Switch(Action<T> onValue, Action onEmpty)
Parameters
| Type | Name | Description |
|---|---|---|
| Action<T> | onValue | Called when the Optional<T> has a value. |
| Action | onEmpty | Called when the Optional<T> does not have a value. |
Switch<TResult>(Func<T, TResult>, Func<TResult>)
Chooses a function to call depending on whether the Optional<T> has a value.
Declaration
public TResult Switch<TResult>(Func<T, TResult> onValue, Func<TResult> onEmpty)
Parameters
| Type | Name | Description |
|---|---|---|
| Func<T, TResult> | onValue | Called when the Optional<T> has a value. |
| Func<TResult> | onEmpty | Called when the Optional<T> does not have a value. |
Returns
| Type | Description |
|---|---|
| TResult | Result of the function that was called. |
Type Parameters
| Name | Description |
|---|---|
| TResult |
Then<TResult>(Func<T, Optional<TResult>>)
Chains another Optional<T>-producing operation onto the result of another.
Declaration
public Optional<TResult> Then<TResult>(Func<T, Optional<TResult>> f)
Parameters
| Type | Name | Description |
|---|---|---|
| Func<T, Optional<TResult>> | f |
Returns
| Type | Description |
|---|---|
| Optional<TResult> |
Type Parameters
| Name | Description |
|---|---|
| TResult |
Remarks
This is a monad bind operation.
Conceptually, it is the same as passing f to OnValue<TResult>(Func<T, TResult>)
and then "flattening" the Optionlt;Optional< into an T>>Optional<.
(Note that T>Optionlt;Optional< is not a valid Optional<T> because of the
type constraint T>>where T : class.)
ToEnumerable()
Converts an optional value to an enumerable. The enumerable will have either zero or one elements.
Declaration
public IEnumerable<T> ToEnumerable()
Returns
| Type | Description |
|---|---|
| IEnumerable<T> |
ToString()
Returns the value's string representation, or a localized "none" message.
Declaration
public override string ToString()
Returns
| Type | Description |
|---|---|
| String |
Overrides
| Improve this Doc View SourceValueOr(T)
Extracts the value with a fallback if the Optional<T> is empty.
Declaration
public T ValueOr(T fallback)
Parameters
| Type | Name | Description |
|---|---|---|
| T | fallback |
Returns
| Type | Description |
|---|---|
| T |
Operators
| Improve this Doc View SourceEquality(Optional<T>, Optional<T>)
Determines whether two instances of Optional<T> have the same value.
Declaration
public static bool operator ==(Optional<T> lhs, Optional<T> rhs)
Parameters
| Type | Name | Description |
|---|---|---|
| Optional<T> | lhs | |
| Optional<T> | rhs |
Returns
| Type | Description |
|---|---|
| Boolean |
Explicit(Optional<T> to T)
Casts this instance to its underlying value or the default value for the underlying type.
Declaration
public static explicit operator T(Optional<T> optional)
Parameters
| Type | Name | Description |
|---|---|---|
| Optional<T> | optional |
Returns
| Type | Description |
|---|---|
| T |
Implicit(T to Optional<T>)
Converts an instance of a type to an optional value.
Declaration
public static implicit operator Optional<T>(T value)
Parameters
| Type | Name | Description |
|---|---|---|
| T | value |
Returns
| Type | Description |
|---|---|
| Optional<T> |
Inequality(Optional<T>, Optional<T>)
Determines whether two instances of Optional<T> have different values.
Declaration
public static bool operator !=(Optional<T> lhs, Optional<T> rhs)
Parameters
| Type | Name | Description |
|---|---|---|
| Optional<T> | lhs | |
| Optional<T> | rhs |
Returns
| Type | Description |
|---|---|
| Boolean |