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 |