There is a third way which is to change the signature of the method to not return the object itself, but a wrapper around it. The wrapper contains the value (if there is one), a flag property to indicate whether there is a value (e.g. HasValue) and a method or property for retrieval of the value (which throws if there is none). In short, it provides a type-safe way of representing null.
This is the preferred approach in some of the more functional languages, see the Option type in Scala and Maybe in Haskell, for example.
// example C# using a theoretical Option class
Option<Person> optionallyBob = LookupPerson("Bob");
if (optionallyBob == Option<Person>.None) ... // handle no value
if (!optionallyBob.HasValue) ... // alternative way of handling no value
Bob bob = optionallyBob.Value; // will throw, unless we have already checked there is a value
public Option<Person> LookupPerson(string name)
{
if (couldNotFindInDatabaseEtc) return Option<Person>.None;
return Option.Some(new Person("name"));
}
The advantages to this approach are:
- The caller is forced to deal with the return value as they are not returned the desired type directly, instead they are returned
Option<WhatWasWanted>
instead, so they cannot blindly pass it on to a method that expects a WhatWasWanted
.
- The caller can opt not to check whether the
Option
contains a value and know that they will get an exception if it is, indeed, empty. This gives the caller control over whether they get an exception or not without adding kludges to the API (such as a flag for whether to throw or Try
overloads that don't throw).
I have a class, Box
which I use on my projects for this purpose, the code is below which you may freely use. (You may want to consider renaming it: I have some reservations about the name due to the use of 'box' already in primitve value object wrapping, although it is the same premise. Also, I have been thinking it would be better of as a struct as, as a class, one can assign a null to a Box
reference!)
Edit: I've taken this opportunity to revist this class and have made some a bit closer to the Option type in Scala. By having the separate Some and None types, it also means that the empty check can be avoided in the call to Value and that the types can be made structs (before it had to be a class to allow mulitple constructors).
/// <summary>
/// Option helper class.
/// </summary>
public static class Option
{
public static Option<TItem> Some<TItem>(TItem item)
{
return new Some<TItem>(item);
}
public static Option<TItem> None<TItem>()
{
return new None<TItem>();
}
}
/// <summary>
/// An optional value.
/// </summary>
/// <typeparam name="TItem"></typeparam>
public interface Option<out TItem>
{
/// <summary>
/// Whether the optional value actually has a value.
/// </summary>
bool HasValue { get; }
/// <summary>
/// The value.
/// </summary>
/// <exception cref="InvalidOperationException">The optional value has no value.</exception>
TItem Value { get; }
}
/// <summary>
/// Some value: an optional value that actually has a value.
/// </summary>
/// <typeparam name="TItem"></typeparam>
public struct Some<TItem> : Option<TItem>, IEquatable<Some<TItem>>, IEquatable<None<TItem>>
{
/// <summary>
/// Constructs a new optional value with value.
/// </summary>
/// <param name="item">The value.</param>
public Some(TItem item)
{
this.item = item;
}
/// <summary>
/// Whether the optional value actually has a value.
///
/// Returns true always for Some.
/// </summary>
public bool HasValue
{
get { return true; }
}
/// <summary>
/// The value.
///
/// Does not throw.
/// </summary>
public TItem Value
{
get { return this.item; }
}
public bool Equals(Some<TItem> other)
{
return Equals(other.item, this.item);
}
public bool Equals(None<TItem> other)
{
return false;
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (obj.GetType() != typeof (Some<TItem>)) return false;
return Equals((Some<TItem>) obj);
}
public override int GetHashCode()
{
return this.item.GetHashCode();
}
public static bool operator ==(Some<TItem> left, Some<TItem> right)
{
return left.Equals(right);
}
public static bool operator ==(Some<TItem> left, None<TItem> right)
{
return false;
}
public static bool operator !=(Some<TItem> left, Some<TItem> right)
{
return !left.Equals(right);
}
public static bool operator !=(Some<TItem> left, None<TItem> right)
{
return true;
}
private readonly TItem item;
}
/// <summary>
/// No value: an optional value that has no value.
/// </summary>
/// <typeparam name="TItem"></typeparam>
public struct None<TItem> : Option<TItem>, IEquatable<None<TItem>>, IEquatable<Some<TItem>>
{
/// <summary>
/// Whether the optional value actually has a value.
///
/// Returns false always for none.
/// </summary>
public bool HasValue
{
get { return false; }
}
/// <summary>
/// The value.
///
/// Throws always.
/// </summary>
public TItem Value
{
get { throw new InvalidOperationException("No value to retrieve."); }
}
public bool Equals(None<TItem> other)
{
return true;
}
public bool Equals(Some<TItem> other)
{
return false;
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (obj.GetType() != typeof (None<TItem>)) return false;
return Equals((None<TItem>) obj);
}
public override int GetHashCode()
{
return 0;
}
public static bool operator ==(None<TItem> left, None<TItem> right)
{
return left.Equals(right);
}
public static bool operator ==(None<TItem> left, Some<TItem> right)
{
return false;
}
public static bool operator !=(None<TItem> left, None<TItem> right)
{
return !left.Equals(right);
}
public static bool operator !=(None<TItem> left, Some<TItem> right)
{
return true;
}
}