by Zoran Horvat
We have already been discussing the Option/Maybe functional type from two angles. The short article Understanding the Option (Maybe) Functional Type gives reasons why we might need the Option type. In shortest, optional objects are most effectively modeled using the Option type. Common alternative to this is to use null, which is a bad practice. Null requires constant checks or otherwise code might fail at run time with the NullReferenceException. Null checks, on the other hand, are imperative and procedural, which leads to less flexible and less extendible object-oriented code.
The other article, which was part of the series on reducing cyclomatic complexity , gives a different view on the matter of optional objects. If mapped with imperative constructs, such as null references and null guards, optional objects cause cyclomatic complexity of code to grow. This makes number of possible execution paths larger, making it harder to think about it, and even harder to extend it later.
You can learn more about this thinking method from the video course Making Your C# Code More Functional published at Pluralsight .
In this article, you will learn how to a design and implement a complete custom Option type, together with extensions that will make it fit nicely with other .NET types, especially with collections and LINQ.
It is a pity to know that C# language still doesn’t support optional objects at syntactic level. Having a keyword, or an operator, and specific compiler behavior to indicate optional object would really be great. While that solution comes, we have to build our own replacement types. That has the drawback in sense that code will quickly become verbose – exactly what syntactic constructs are good at solving. In this article, we will build a complete implementation of the Option type which can be included in any object-oriented C# code.
In the previous article on reducing cyclomatic complexity , you could see a very simple (even overly primitive) implementation which is based on the idea that Option can be viewed as a sequence of objects containing no more than one of them.
Optional object is characterized by the fact that it might exist or might not exist. There is no null, no other notion but only having an object or having no object. In that respect, IEnumerable<T> comes to the rescue. Here is the example:
IEnumerable<Car> some = new[] { blueCar };
IEnumerable<Car> none = new Car[0];
This is the easiest way to make a distinction between an existing object (some) and a nonexistent object (none). And there is more. From this point on, you can use the entire LINQ library to perform operations on optional objects.
For instance, you might wish to transform an optional object. Since you don’t know whether the object exists in the first place, you can only optionally transform it – effectively obtaining an option of the target type.
IEnumerable<Car> maybeCar = owner.TryGetCar();
IEnumerable<Color> maybeColor = maybeCar.Select(car => car.BodyColor);
Or you might wish to know how many people out there are driving red cars, although some people might not even have a car. Here is the query:
IEnumerable<Person> people = ...
int redCars =
people.SelectMany(person => person.TryGetCar())
.Count(car => car.BodyColor == Color.Red);
In this piece of code, I am literally selecting all the cars optionally owned by people and then counting those painted in red.
Representing optional objects with IEnumerable<T> comes with a drawback. It doesn’t convey the purpose correctly. IEnumerable<T> indicates a sequence which implements the Iterator design pattern. Option is something else. It is an object which either contains one piece of content in it or contains none. It would be like an IEnumerable<T> sequence limited to contain at most one object. However, IEnumerable<T>’s contract doesn’t come with such constraint.
And where one type isn’t constrained enough, we can define a subtype which adds a constraint. That is how subtyping works in object-oriented languages. With interface-based polymorphism, subtyping becomes even easier.
Therefore, the idea is to define a new type, and call it Option<T>, which implements the IEnumerable<T>. However, this type would not allow an instance which contains more than one element in it. If you remember, that was the precise reason why naked IEnumerable<T> was not suitable to represent optional objects.
Here is the complete implementation of the Option<T> type which implements (and specializes) IEnumerable<T>:
class Option<T> : IEnumerable<T>
{
public T[] Content { get; }
private Option(T[] content)
{
this.Content = content;
}
public static Option<T> Some(T value) =>
new Option<T>(new[] {value});
public static Option<T> None() =>
new Option<T>(new T[0]);
public IEnumerator<T> GetEnumerator() =>
((IEnumerable<T>) this.Content).GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() =>
GetEnumerator();
}
And that is all we had to do to implement the Option type. This implementation is based on the idea that the only way for the consumer to instantiate the Option is through one of the two public static factory methods – one named Some, and the other named None. Constructor is declared private and in that way the consumer is constrained to either wrap one and only one object in an option, or to wrap no object.
When the time comes to consume the option, we can remember that it implements IEnumerable<T>, which will let us apply the entire LINQ library to optional objects. For example, we might consider a public interface of the Person class, which exposes a TryGetCar() method returning an option of Car type. When the time comes to count red cars among people who optionally own cars, we would use the LINQ’s SelectMany() extension method to pick all the optional cars that are Some.
class Person
{
public Option<Car> TryGetCar() => ...
}
...
public int CountRedCars(IEnumerable<Person> people) =>
people.SelectMany(person => person.TryGetCar())
.Count(car => car.BodyColor == Color.Red);
Option implementing IEnumerable<T> may be convenient as it takes no more than twenty lines of code to implement it. Consuming such an option is not that convenient. Quite the contrary, there is no visual distinction between an optional object, which contains at most one contained value, and a proper sequence containing unbounded number of values.
It would be better if we could separate optional objects from sequences and other data structures altogether and design a single meaningful public API to work with them. That is what we will do next.
When designing an entirely new type, we have great freedom to interpret requirements in a way which fits best with the concepts of object-oriented programming. Option type will be no different. In this exercise, I will demonstrate a design technique which yields highly usable types for a given set of requirements.
Functional requirement for the Option type is that its instance may either contain a value or contain no value. Non-functional requirement is that the public interface should allow seamless integration of the Option type with the rest of the code.
Let me show you what that means on a simple example. Imagine that we need to implement the TryGetCar method of the Person class. A person is allowed to have a car if she is at least 18 years old. Otherwise, the person cannot have a car. That is precisely the purpose of optional objects. The requirement could be to design the TryGetCar method so that it returns an optional Car object.
Code is worth a thousand words, so here is the desired implementation of the Person class.
class Person
{
private DateTime BirthDate { get; }
public Person(DateTime birthDate)
{
this.BirthDate = birthDate.Date;
}
public Option<Car> TryGetCar(DateTime at)
{
if (this.BirthDate.AddYears(18) <= at)
return new Car();
return none;
}
}
This implementation is straight-forward. If there is a car, the function simply returns the car object. Otherwise, if there is no car, the function returns some mysterious value none. The most important part here is to notice how the function is either returning a contained value or returning none, quite literally. There are no conversions, wrappers, nothing. Just return what you think is right.
How do we proceed if we know that this implementation above is the desired look of the Option type consumer? We can proceed in top-down style by understanding that there must be a generic type Option<T>. The next mandatory feature will be the automatic conversion from type T to its corresponding Option<T>. We can see this requirement in the return line where new car object is returned as if it were a proper Option<Car> object. And the last functional requirement will be that this final mysterious value none must be returnable as an option of any type.
These three requirements are all that we have in the beginning, and we can implement them directly with the following set of related types.
public abstract class Option<T>
{
public static implicit operator Option<T>(T value) =>
new Some<T>(value);
public static implicit operator Option<T>(None none) =>
new None<T>();
}
public sealed class Some<T> : Option<T>
{
public T Content { get; }
public Some(T value)
{
this.Content = value;
}
}
public sealed class None<T> : Option<T>
{
}
public sealed class None
{
public static None Value { get; } = new None();
private None() { }
}
This is the entire implementation, and now we will analyze what this code means and how it will work.
The Option<T> type is an abstract class with two derived classes: Some<T> and None<T>. Some is a concrete class which wraps an object of generic argument T. None, on the other hand, is purely declarative and it contains no state.
When the time comes to produce an option, you can choose to either construct an instance of the Some<T> class (and give it a content, by the way), or to construct an empty instance of the None<T> class.
Option base class helps with instantiation by providing the two implicit conversion operators. The first operator can convert any value of type T into an option of the same type T. This is very convenient, because it allows us to assign a proper object to a reference of an optional type:
Option<Car> optional = new Car();
The second operator is even more interesting. It converts an object of the non-generic None type into an option of some type T. This might sound impossible, as the None object which is being converted doesn’t know the optional content T which it should support. Well, that can be resolved very easily if we understand that all nones in the world are the same. As the result, I’ve been able to write the non-generic None class and implement it as the Singleton. There will always be only one value of the None class, and that value will be convertible to an option of any type T you could imagine. Here is the example which works just fine:
Option<Car> optional = None.Value;
To make the original code of the TryGetCar function complete, we should return None.Value in the final line:
public Option<Car> TryGetCar(DateTime at)
{
if (this.BirthDate.AddYears(18) <= at)
return new Car();
return None.Value;
}
This completes the implementation of the Option<T> type which can be constructed conveniently. We are still lacking any means of consuming options. Therefore, the next task for us will be to develop a proper and useful API so that the consumers can seamlessly manipulate optional objects.
When the time comes to use an Option<Car> returned from the TryGetCar method, we are put before the choice. Should we know whether the option contains a Car object, or should we remain ignorant? The first approach is often seen in practice, and it usually produces code with this structure:
if (optional is some)
DoSomething(optional.Content);
else
DoNothing();
In my opinion, this approach is substantially flawed, as it ignores the fact that the optional object is, well, optional, and should be accepted as an uncertainty. How would that be supposed to work? – you may ask. Let me demonstrate the desired on an example.
A person might have a car, or might have none. The person has an optional car. Then, what is the color of the car the person has? Will that be a color? No – it will be an optional color.
Option<Color> maybeColor = person
.TryGetCar() // returns Option<Car>
.Map(car => car.Color); // returns Option<Color>
This is the principal method of working with optional objects. An option of type T can be mapped to any other type TNew by applying a function Func<T, TNew> to its content. The trick is that the mapping function will only be applied if the option contains a value and ignored otherwise. But that is not our job to think about – that is what option will do for us privately.
We could also think of another mapping method, the one which receives a function producing another Option. Now this is funny, and it also reveals how rich the communication can be between optional objects. Let’s see the example:
Option<Person> maybeAMan = ... // Either Some or None
Option<Color> maybeAColor = maybeAPerson // Option<Person>
.MapOptional(person => person.TryGetCar()) // Option<Car>
.Map(car => car.Color); // Option<Color>
In this example, the TryGetCar method is invoked on a Person. But we don’t know if we have the Person in the first place. For that reason, we are using the new function MapOptional, which receives a Func<T, Option<TResult>>. If this is confusing you, then please refer to the source code given below for clarification. When this kind of mapping is required, then there is one additional execution scenario which didn’t exist in the simple Map function. When executed on None, the result will automatically be None, without invoking the mapping function itself. Only if MapOptional is invoked on a Some object, the mapping function will be invoked, and its result returned as the overall mapping result – either Some or None.
But how will this chain of optional objects end? We could expect that at some point, someone down the stream will know the replacement value for the case that the option was empty. For that case, we could invent a method which reduces the option to its contained type. This method would have to receive a replacement value, which would be used in case that the option contains no value.
string colorLabel = person
.TryGetCar()
.Map(car => car.Color.Name)
.Reduce("no car");
The call to Reduce would produce a string contained in the option if there is one; otherwise, it will produce a replacement value “no car” as its result.
In some cases, this approach might not be appropriate. Namely, the replacement value must be passed to the Reduce method regardless of whether it will be used or not. But preparing the replacement value could be a costly operation. It might require a round-trip to the database, for example. For that case, we might come up with a lazy Reduce method, the one receiving a lambda which produces the replacement value only when required.
string colorLabel = person
.TryGetCar()
.Map(car => car.Color.Name)
.Reduce(() => labelsDictionary[noColorLabelId]);
This implementation would read a label from the dictionary in case that there is no real car color available. But the dictionary lookup would only be done if there is really no car available. This strategy can be useful to improve efficiency of the code which deals with optional objects.
Below is the complete source code of the Option type and its subtypes, with Map and Reduce functions added.
public abstract class Option<T>
{
public static implicit operator Option<T>(T value) =>
new Some<T>(value);
public static implicit operator Option<T>(None none) =>
new None<T>();
public abstract Option<TResult> Map<TResult>(Func<T, TResult> map);
public abstract Option<TResult> MapOptional<TResult>(Func<T, Option<TResult>> map);
public abstract T Reduce(T whenNone);
public abstract T Reduce(Func<T> whenNone);
}
public sealed class Some<T> : Option<T>
{
public T Content { get; }
public Some(T value)
{
this.Content = value;
}
public static implicit operator T(Some<T> some) =>
some.Content;
public override Option<TResult> Map<TResult>(Func<T, TResult> map) =>
map(this.Content);
public override Option<TResult> MapOptional<TResult>(Func<T, Option<TResult>> map) =>
map(this.Content);
public override T Reduce(T whenNone) =>
this.Content;
public override T Reduce(Func<T> whenNone) =>
this.Content;
}
public sealed class None<T> : Option<T>
{
public override Option<TResult> Map<TResult>(Func<T, TResult> map) =>
None.Value;
public override Option<TResult> MapOptional<TResult>(Func<T, Option<TResult>> map) =>
None.Value;
public override T Reduce(T whenNone) =>
whenNone;
public override T Reduce(Func<T> whenNone) =>
whenNone();
}
public sealed class None
{
public static None Value { get; } = new None();
private None() { }
}
This completes the implementation of optional mapping features. There is one more detail added to the code above. The Some<T> class now includes an implicit conversion operator to the contained type T. In cases when you already have a reference to some, you can conveniently use that reference in expressions in place of objects of type T.
And we could stop at this point. With the code we have, we could easily construct and then consume optional objects in a convenient and readable way. But we could also choose to continue building reusable behavior patterns around optional objects. Supporting null is one feature we often need in practice. Equality testing could be another. Or supporting sequences of optional objects, you name it.
In the remainder of this article, we will provide several isolated features that are making the Option type even easier to use, at the expense of having to learn a larger API that comes packed with it.
Long ago was the time when we had to use null to indicate missing objects. Programmers have gradually gained understanding that null references are of little value to the model, but can cause great deal of damage should they forget to guard. NullReferenceException has become the most frequent kind of exception in everyday software execution.
When we need to model a missing object, then that is an obvious case of optional objects. Therefore, the Option type is an excellent replacement for null references. That brings benefits on several accounts. Most obviously, the concepts will be named more precisely. Take the function of the Person class which returns the person’s car. What would you expect this function to return in case when the person possesses no car?
public Car GetCar() { ... }
This is precisely the situation in which so many programmers decide to return null in case of a missing object. But that is the design flaw! The function is clearly indicating that it will return an object of type Car. Yet, when the function returns null, then it has failed to return an object. An unsuspecting consumer of this function might forget to guard against null result and fail in an unhandled NullReferenceException.
Let’s turn that into an optional object, then:
public Option<Car> GetCar() { ... }
This function signature is carrying more meaning than the previous one. It clearly specifies that the function might return a Car object or might return none. There is no room left to speculate about possible returns from this function. On the consuming end, the caller will not be able to access any Car object unless it has successfully obtained a reference to Some<Car> object. The Option<Car> itself will not give out any Car reference, simply because the option itself cannot make any promises regarding the existence of any Car object in it.
That should be all the reasons you need to avoid using null in favor of using the optional objects. We could even enhance the Option API to support null tests out of the box. This could be the desired behavior on the consuming end:
Color color = Color.Red;
Option<Color> maybeColor1 = color.NoneIfNull(); // = Some
color = null;
Option<Color> maybeColor2 = color.NoneIfNull(); // = None
We could attach the NoneIfNull extension method to any type and expect it to produce Some of that type only if the object to which the function is applied is non-null. Otherwise, NoneIfNull would, as its name implies, return None.
Before we implement this feature, we could come up with a similar feature which turns any object into its optional counterpart, based on a Boolean condition:
color = Color.Red;
Option<Color> beautiful = color.When(color == Color.Red); // = Some
color = Color.Blue;
Option<Color> ugly = color.When(c => c == Color.Red); // = None
There are two variants of the When method demonstrated here. One is receiving a Boolean value and constructing an Option based on that value. The overload is receiving a predicate, a Func<T, bool> delegate, which will be used to calculate the Boolean flag.
Therefore, we can conclude that these two (or three) methods can be used to turn any object into an optional object, either by testing it against null, or by testing some custom logic. With requirements done, we can easily implement an extensions class with three extension methods which will behave as shown in the example above.
public static class ObjectExtensions
{
public static Option<T> When<T>(this T obj, bool condition) =>
condition ? (Option<T>)new Some<T>(obj) : None.Value;
public static Option<T> When<T>(this T obj, Func<T, bool> predicate) =>
obj.When(predicate(obj));
public static Option<T> NoneIfNull<T>(this T obj) =>
obj.When(!object.ReferenceEquals(obj, null));
}
Good thing about this implementation is that it is based on extension methods. We are not polluting the Option class’s interface. New members are attached to any type by including the namespace in which this extension class is defined.
This completes the support for references. Beyond this point, we will never need to use null again when modeling any domain. The next task for us will be to integrate options with sequences and dictionaries.
LINQ defines the FirstOrDefault operator as a soft variant of the more rigid First operator. The latter has a nasty habit of throwing in case the sequence is empty, consequently being unable to procure the first element out of none. The FirstOrDefault operator attempts to help by introducing a default value for the underlying sequence type as a substitute in case that the real first element cannot be fetched. When the sequence contains elements of a reference type, and it is currently empty, then call to FirstOrDefault would return null.
On the second look at the empty sequence, and knowing that we have optional objects at our disposal, we could say that the first element of a sequence is an optional object. An empty sequence has no first element (or has none, if you like). A non-empty sequence has a real first element (or has some element, conversely).
Therefore, we could introduce a new operator, and call it FirstOrNone. As its name implies, this operator would either return the first element of the sequence, wrapped in a Some object, or return a None in case that the sequence contained no elements. Consider the following example:
IEnumerable<Color> colors = new[]
{
Color.Red, Color.Blue
};
new Color[0].FirstOrNone(); // None
colors.FirstOrNone(); // Some(Red)
colors.FirstOrNone(c => c == Color.Green); // None
Unlike the existing First and FirstOrDefault operators, the custom FirstOrNone operator would never fail and never return null.
There is another useful operator that comes to mind. The most important LINQ operator is Select, and it maps every object of a sequence into some other object, potentially of a different type. The Select operator is used to map a sequence of one type into a sequence of another type with the same number of elements. But what if the mapping function is returning an optional object?
Consider this example:
IEnumerable<Person> people = new[]
{
new Person(9, Color.Green), // No car
new Person(19, Color.Red), // Has a red car
new Person(22, Color.Blue) // Has a blue car
};
people.Select(person => person.TryGetCar()); // IEnumerable<Option<Car>>
In this code segment, we are querying a sequence of Person objects for their cars. But not all people have cars, and the resulting sequence is in fact a sequence of car options. That is probably not what we wanted to have. I would rather expect to have a proper sequence of cars, an IEnumerable<Car> instead. However, the common Select operator cannot promise to extract contained objects because it has no notion of optional objects.
What we can do instead is to introduce another custom operator, the one which is aware of the optional objects, and which would be able to extract objects contained in Some, while at the same time ignoring any None objects that might be produced by the mapping function. We could call that custom operator SelectOptional, and it would receive a mapping function which returns an optional result, just like the TryGetCar method is doing on the Person class from the example.
Here is how we would expect the SelectOptional operator to behave:
IEnumerable<Person> people = new[]
{
new Person(9, Color.Green), // No car
new Person(19, Color.Red), // Has a red car
new Person(22, Color.Blue) // Has a blue car
};
IEnumerable<Color> carColors =
people.SelectOptional(person => person.TryGetCar())
.Select(car => car.Color); // [Red, Blue]
In this example, we are selecting optional cars, and then, in the continuation of the same expression, we are fetching the color of each of the cars. The trick in this expression is that the sequence of cars will only contain true Car objects. That is what makes it possible to extract car colors in the subsequent Select without fearing from failure of any kind.
By this point we have identified need for two operators on IEnumerable: FirstOrNone and SelectOptional. Below is the implementation. FirstOrNone operator is coming with an overload which accepts a predicate. This idea is similar to the one that already exists in the First and FirstOrDefault operators.
public static class EnumerableExtensions
{
public static Option<T> FirstOrNone<T>(this IEnumerable<T> sequence) =>
sequence.Select(x => (Option<T>)new Some<T>(x))
.DefaultIfEmpty(None.Value)
.First();
public static Option<T> FirstOrNone<T>(
this IEnumerable<T> sequence, Func<T, bool> predicate) =>
sequence.Where(predicate).FirstOrNone();
public static IEnumerable<TResult> SelectOptional<T, TResult>(
this IEnumerable<T> sequence, Func<T, Option<TResult>> map) =>
sequence.Select(map)
.OfType<Some<TResult>>()
.Select(some => some.Content);
}
With these two operators, we have made it easy to work with sequences of optional objects. In the next exercise, we will apply similar logic to dictionaries, where the question whether an object exists in the dictionary or not will also be treated as the existence of optional objects.
Dictionaries are used to map keys to values. Their API, when attempting to read a value given a key is, however, is awkward, to say the least. The famous TryGetValue method, which receives an out parameter for the value and returns a Boolean flag to tell whether the out parameter was populated or not, is anything but object-oriented. Here is a short example which demonstrates how the dictionary would be used in traditional style.
First, we would populate the dictionary. In this example, I will use optional objects and custom LINQ operators introduced above to show how they can be applied in a context of a larger operation.
IEnumerable<Person> people = new[]
{
new Person("Jack", 9, Color.Green), // No car
new Person("Jill", 19, Color.Red), // Has a red car
new Person("Joe", 22, Color.Blue) // Has a blue car
};
IDictionary<string, Car> nameToCar = people // IEnumerable<Person>
.SelectOptional(person =>
person.TryGetCar() // Option<Car>
.Map(car => (name: person.Name, car: car)) // Option<(string, Car)>
) // IEnumerable<(string, Car)>
.ToDictionary(
tuple => tuple.name, // key = name
tuple => tuple.car); // value = car
In this code segment, we are mapping people’s names to the cars they own. However, not every person has a car. For that reason, the SelectOptional operator is used to extract only those name/car pairs that really exist. You can see from the annotations on the right-hand side of every line the precise type produced by that line of code. The data are gradually transformed from a Person object, to an optional car, to an optional name/car pair, to become a proper sequence of name/car pairs after the SelectOptional operator is done with them. The last call to the ToDictionary LINQ operator will turn these pairs into a dictionary, where the person’s name becomes the key and the associated car becomes the value.
And that is where the adventure with the dictionary only begins. With the dictionary at hand, we can look up different names and try to get the Car objects. Traditionally, we would use the TryGetValue method for lookup when we are not certain whether the dictionary contains the lookup key or not.
Here is what it would usually look like:
if (nameToCar.TryGetValue("Jill", out Car jillsCar))
Console.WriteLine(jillsCar); // *** Prints this
else
Console.WriteLine("No car for Jill");
if (nameToCar.TryGetValue("Jimmy", out Car jimmysCar))
Console.WriteLine(jimmysCar);
else
Console.WriteLine("No car for Jimmy"); // *** Prints this
This code segment is exhibiting several traits that we normally avoid in any object-oriented code. The function is communicating its result through an out parameter, which makes TryGetValue a function with side effects. Although side effects are not automatically a bad thing, they still incur higher-than-usual cognitive pressure. We must think harder to read this code properly. For example, the out parameter will be set to the type’s default if the method has returned False. In reference types, that would be null. Well, that hides an obvious danger, because an unsuspecting caller might receive a NullReferenceException if it forgot to test the return value.
Another, much worse trait, is the Boolean return value. Instead of doing something for us, the function is returning a flat true or false flag, handing all the remaining work to us. That is not what objects are supposed to do. They are supposed to perform an operation, not just to tell the caller what it should do. And, when done this way, with the Boolean return flag, the calling code takes a well known if-else structure, which is quite verbose and prone to errors.
We will now try to fix this shortcoming of .NET dictionaries by first giving things their proper names. When an object is looked up by its key in the dictionary, the result might be the object sought or no object. The latter case occurs when there is no object stored in the dictionary under the specified key. Therefore, the dictionary lookup yields an optional result, not an object and a Boolean flag indicating whether we should be safe to touch it or not.
Therefore, the suggested design would be to implement a new TryGetValue method on the dictionary, such that it receives the key and returns an optional object as the result. This method would have no side effects. Its return value would hold the safe handle which makes it impossible to access a nonexistent object, including that any possibility of a NullReferenceException is removed.
This new method would be used in a much shorter way:
Console.WriteLine(nameToCar.TryGetValue("Jill")); // Prints Some
Console.WriteLine(nameToCar.TryGetValue("Jimmy")); // Prints None
With the optional Car at hand, we can apply any mappings we need, knowing that only the actual Car objects will ever be accessed. That improves code stability, while at the same time making it significantly shorter compared to if-else structures.
Now that we have the desired API, it will be easy to attach a new method to all IDictionary<TKey, TValue> implementations:
public static class DictionaryExtensions
{
public static Option<TValue> TryGetValue<TKey, TValue>(
this IDictionary<TKey, TValue> dictionary, TKey key) =>
dictionary.TryGetValue(key, out TValue value)
? (Option<TValue>)new Some<TValue>(value)
: None.Value;
}
This operator is taking the key as the argument and returning an optional value as the result. It has no side effects and causes no branching at the calling site.
When a class derives from its base, then substitution principle is in place, allowing us to set a reference to the base type to refer to an object of a derived type.
public abstract class Vehicle { ... }
public class Car : Vehicle { ... }
Vehicle vehicle = new Car();
One would then expect that optional objects are also substitutable, like in this line:
Option<Vehicle> vehicle = (Option<Car>)new Car();
Unfortunately, that cannot be supported because Option’s API is both receiving and returning values of its contained type. If options were covariant, then the following code block would fail at run time:
Option<Vehicle> vehicle = (Option<Car>)new Car();
Vehicle existingOne = vehicle.Reduce(new Truck());
This code wouldn’t work because the Car instance on the right-hand side of the first assignment line only exposes a Reduce method which receives another Car instance:
Car Option<Car>.Reduce(Car whenNone);
Allowing a call with a Truck instance to be made, would effectively cause the Reduce method to return an instance which is not a Car, despite the method’s signature which promises to return nothing else but a Car. All that was said here makes no sense, which means that the Option type is neither covariant nor contravariant, but invariant instead.
We can still support conversion to base optional type by adding a method which would explicitly convert an option.
Option<Car> car = (Option<Car>)new Car();
Option<Vehicle> vehicle = car.OfType<Vehicle>();
This operator would turn an option of some type T into an option of another type. However, the relation between these two types will decide how exactly the transformation will work. If the new type is assignable from the existing type, and the option is Some, then the resulting object would be the Some of the destination type. It would then contain the same value as the original option, only this time cast to the new type. On the other hand, if the option was already None, or its contained type is not assignable to the new type, then the result of the operation will be None of the new type.
This might wound confusing, but it’s in fact very simple. Here are the examples which are depicting all the possibilities.
Option<Car> someCar = new Car("car", Color.Red);
Option<Car> noCar = None.Value;
someCar.OfType<Vehicle>(); // Some
noCar.OfType<Vehicle>(); // None
someCar.OfType<Truck>(); // None
Now that the desired behavior is defined, we can easily implement this method on the base Option class.
public abstract class Option<T>
{
...
public Option<TNew> OfType<TNew>() where TNew : class =>
this is Some<T> some && typeof(TNew).IsAssignableFrom(typeof(T))
? (Option<TNew>)new Some<TNew>(some.Content as TNew)
: None.Value;
}
Note that this transformation is only possible if the destination type is a reference type. Hence the where TNew : class generic type constraint. This is mandatory because value types cannot be cast to any other type in C#.
It is tempting to think of Some object as being equivalent to the content it wraps. Wouldn’t it be great if the following piece of code could find the two objects equal?
Option<Car> some = car;
if (some.Equals(car))
{
Console.WriteLine("Yeah!");
}
However, this ideal can never be met. If you have reserves about how exactly Equals and GetHashCode overloads should be implemented, you can refer to How to Override Equals and GetHashCode Methods in Base and Derived Classes . The most important part is that these methods are meant to implement the mathematical relation of equivalence. This relation is defined as reflexive, symmetric and transitive. Now, symmetricity of the equivalence relation is the attribute which fights any thought that we could test equality of an optional object and a proper object.
Consider an implementation of the Option<T> type such that it can compare itself with objects of type T. In such solution, we would return True from a Some of T when compared with the same object of type T as the one wrapped inside the Some instance:
Some<Color> some = Color.Red;
some.Equals(Color.Red); // Should be True?
This behavior is possible because we are in charge of implementing the Some<T> type and we can make it behave in any way we like – one kind of behavior being to compare its contained value with the value received in the Equals method. The problem is on the other end of this relationship. Equivalence relation is symmetric, which means that A is related to B if and only if B is also related to A.
In other words, a call to Equals on a Some<T> object must always return the same result as the corresponding call on an object of type T:
Some<Color> some = Color.Red;
some.Equals(Color.Red) == Color.Red.Equals(some); // Must be True
And that is how we are arriving at the essence of the question. Shall we implement all types T to compare themselves with the content of the corresponding optional type? This would have to hold true in such case:
aColor.Equals(new Some<Color>(aColor)); // Should be True
Neither shall we implement this obscure behavior in any new type, nor will the existing types be able to reproduce this behavior out of the box.
Therefore, the conclusion is that equivalence cannot be implemented in Options out of the box. Apparently, the most we could do is to implement equivalence tests between the two options. Two Nones would be equal. And two Somes of the same type would delegate further comparison to their contained objects and hope for the best.
Here is the expected behavior:
Option<Color> red = Color.Red;
Option<Color> otherRed = Color.Red;
Option<Color> blue = Color.Blue;
red.GetHashCode() == otherRed.GetHashCode(); // True
red.Equals(otherRed); // True
red.Equals(Color.Red); // False
Color.Red.Equals(red); // False
red.Equals(blue); // False
Option<Color> none = None.Value;
Option<Color> otherNone = None.Value;
none.Equals(otherNone); // True
none.Equals(None.Value); // True
None.Value.Equals(none); // True
Please, carefully examine these expressions, as they are demonstrating the symmetry of equality tests between different objects. As usual, after understanding the desired behavior, we can implement the missing members. Below is the code to add to Some and None implementations to make them testable for equality.
public sealed class Some<T> : Option<T>, IEquatable<Some<T>>
{
...
public bool Equals(Some<T> other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return EqualityComparer<T>.Default.Equals(Content, other.Content);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
return obj is Some<T> && Equals((Some<T>) obj);
}
public override int GetHashCode()
{
return EqualityComparer<T>.Default.GetHashCode(Content);
}
public static bool operator ==(Some<T> a, Some<T> b) =>
(a is null && b is null) ||
(!(a is null) && a.Equals(b));
public static bool operator !=(Some<T> a, Some<T> b) => !(a == b);
}
public sealed class None<T> : Option<T>, IEquatable<None<T>>, IEquatable<None>
{
...
public override bool Equals(object obj) =>
!(obj is null) && ((obj is None<T>) || (obj is None));
public override int GetHashCode() => 0;
public bool Equals(None<T> other) => true;
public bool Equals(None other) => true;
public static bool operator ==(None<T> a, None<T> b) =>
(a is null && b is null) ||
(!(a is null) && a.Equals(b));
public static bool operator !=(None<T> a, None<T> b) => !(a == b);
}
public sealed class None : IEquatable<None>
{
...
public override bool Equals(object obj) =>
!(obj is null) && ((obj is None) || this.IsGenericNone(obj.GetType()));
private bool IsGenericNone(Type type) =>
type.GenericTypeArguments.Length == 1 &&
typeof(None<>).MakeGenericType(type.GenericTypeArguments[0]) == type;
public bool Equals(None other) => true;
public override int GetHashCode() => 0;
}
This code is quite verbose, as it usually happens when implementing Equals and GetHashCode. The most difficult part is the equality between None<T> and None types, as they must support each other. In that sense, the non-generic None was in the worse situation because it cannot get hold of the genetic argument T until its Equals is invoked with a proper None<T> object, for some T. You can see the implementation of the IsGenericNone method in the non-generic None class for clarification how this equivalence test was implemented.
Objects implementing an interface can also be optional. How does that fit into the Option API that we have implemented? In fact, all the methods defined and attached to Option<T> and its descendants will work fine. The only problem will be with the conversion operators. The problem is that C# doesn’t allow operators that include interfaces. Let me show you where the issue will become apparent.
public interface ICar
{
string Name { get; }
}
public class Car : Vehicle, ICar
{
public string Name { get; }
...
}
This is the definition of the Car class, with the new detail that it now implements the ICar interface. We can assign a Car instance to the reference of type Option<Car> with no hesitation. And so can we assign the reference to None.Value singleton.
Option<Car> car = new Car("Jill", Color.Red); // Some<Car>
Option<Car> none = None.Value; // None<Car>
Some<Car> some = new Car("Jack", Color.Blue); // OK
Car extracted = some; // OK
The first two lines will compile fine and produce the expected optional objects, Some and None, respectively. The subsequent two lines will successfully package the Car object into the new Some<Car> instance and then unpack it back into a plain Car reference. The magic that hides behind these interesting assignments lies in the implicit conversion operators defined on Option<T> and Some<T>:
public abstract class Option<T>
{
public static implicit operator Option<T>(T value) =>
new Some<T>(value);
public static implicit operator Option<T>(None none) =>
new None<T>();
...
}
public sealed class Some<T> : Option<T>, IEquatable<Some<T>>
{
public static implicit operator Some<T>(T value) =>
new Some<T>(value);
public static implicit operator T(Some<T> some) =>
some.Content;
...
}
Each of the assignments above is utilizing one of the operators in the order of their appearance. The first assignment of a Car object to an Option<Car> uses the implicit conversion of any object T into the Option<T>. The next line, in which Option<Car> is initialized from the None.Value singleton, is using the next operator on Option<T> which enriches any None instance into the full blown None<T> object for a known generic argument T. And the last two lines are based on the two implicit operators defined on the Some<T> class.
Implicit operators are a two-edged sword in programming. They may sometimes make it easier to introduce a defect. Thanks to their implicit nature, you might not be aware that some additional code will execute. But when done right, implicit operators are a great tool which makes code more readable and shorter. Implicit conversions of optional objects are, I believe, of the latter kind. They are letting us pass objects or non-generic None.Value around and rest assured that these objects will always be converted to the target type without much noise.
But the problems will appear if we attempt to use conversion operators on interface types. We might need an optional ICar at some point. Since Car is still an ICar, we might expect a conversion operator to be placed during build. Alas, that will not work, as the next example will show:
Car jillsCar = new Car("Jill", Color.Red);
ICar car = jillsCar; // OK
Option<ICar> optional = car; // Doesn't compile
Option<ICar> another = jillsCar; // OK
The problem is in converting an interface to an optional interface. Other assignments apparently pass the compilation fine. Why would that be?
The reason lies in one peculiarity of the C# language. According to the C# language specification, any implicit conversion between an interface and a class is strictly forbidden without further analysis. If you are interested to dig deeper on this matter, you can refer to Stack Overflow discussion in which Eric Lippert is explaining the motives behind this design decision in C#: Why doesn't this implicit user-defined conversion compile? .
The short explanation why conversion between an interface and a class doesn’t work would be that the class might sometimes implement that very interface. In that case, what appears to be a conversion, would in fact be a cast operation. Being a pointer conversion, cast is a cheap operation. There is no data copying involved, nor will any new object be created in the process. A proper conversion, on the other hand, means to create an entirely new object and copy relevant data to its new state.
If the language allowed conversion between classes and interfaces, then, depending on actual types involved, the call could be treated as a cast or as a custom conversion. One line of code would compile into a cast, and seemingly the same line of code would turn into a call to the custom conversion operator. That becomes increasingly likely when generic types are involved. Language designers have recognized this kind of uncertainty as a danger. Or, as Eric Lippert has put it in the referenced Stack Overflow post: “A cast on an interface value is always treated as a type test because it is almost always possible that the object really is of that type and really does implement that interface. We don't want to deny you the possibility of doing a cheap representation-preserving conversion.”
If you find this topic interesting, then you are free to investigate it further. You will find truly amazing behavior hiding there. One of the peculiarities of this way in which C# compiler is operating is shown in the sample code which astonishingly passes compilation:
Option<ICar> another = new Car("Jill", Color.Red); // OK
The object on the right is of type Car, not ICar (though it does implement ICar). However, Option<T> comes with the implicit conversion operator from T, and T in this case is obviously ICar. Therefore, the compiler will in fact treat the Car object on the right-hand side as the object implementing the interface ICar and still invoke the implicit conversion operator from ICar to Option<ICar>. But if I said that the right-hand object is the ICar up-front, then the compiler would conclude that I’m attempting to convert an interface into a class and that is a no-no.
Option<ICar> another = (ICar)new Car("Jill", Color.Red); // Not OK
Makes you wonder, doesn’t it?
To cut the long story short, Option<T> and its subclasses expose public constructors which can be used to construct concrete objects. This yields a more verbose code but at the same time it fills the syntactic gap caused by implicit conversions of interfaces.
Option<ICar> optional = new Some<ICar>(car); // OK
Option<ICar> none = new None<ICar>(); // OK
Option<ICar> another = None.Value; // OK
In this article you have seen the basic implementation of optional objects in C#, packed together with a number of extensions that can be applied when using them in different settings.
Special attention was paid to ease of construction, where implicit operators are playing an important role. A method which returns an Option of some type T can either return a proper object T or return a singleton None.Value. This makes construction of options as simple as we can get with the C# 7 syntax.
If you wish to see the entire implementation of the Option<T> type, you can download it from the GitHub repository https://github.com/zoran-horvat/option .
Otherwise, if you wish to learn more on optional objects and the wider theory which surrounds them, you can watch a video course at Pluralsight titled Making Your C# Code More Functional .
If you wish to learn more, please watch my latest video courses
In this course, you will learn the basic principles of object-oriented programming, and then learn how to apply those principles to construct an operational and correct code using the C# programming language and .NET.
As the course progresses, you will learn such programming concepts as objects, method resolution, polymorphism, object composition, class inheritance, object substitution, etc., but also the basic principles of object-oriented design and even project management, such as abstraction, dependency injection, open-closed principle, tell don't ask principle, the principles of agile software development and many more.
More...
In this course, you will learn how design patterns can be applied to make code better: flexible, short, readable.
You will learn how to decide when and which pattern to apply by formally analyzing the need to flex around specific axis.
More...
This course begins with examination of a realistic application, which is poorly factored and doesn't incorporate design patterns. It is nearly impossible to maintain and develop this application further, due to its poor structure and design.
As demonstration after demonstration will unfold, we will refactor this entire application, fitting many design patterns into place almost without effort. By the end of the course, you will know how code refactoring and design patterns can operate together, and help each other create great design.
More...
In four and a half hours of this course, you will learn how to control design of classes, design of complex algorithms, and how to recognize and implement data structures.
After completing this course, you will know how to develop a large and complex domain model, which you will be able to maintain and extend further. And, not to forget, the model you develop in this way will be correct and free of bugs.
More...
Zoran Horvat is the Principal Consultant at Coding Helmet, speaker and author of 100+ articles, and independent trainer on .NET technology stack. He can often be found speaking at conferences and user groups, promoting object-oriented and functional development style and clean coding practices and techniques that improve longevity of complex business applications.