Unity C# Tip: Better Optional References

TL;DR

Use the lightweight Option library to have more performant, less error-prone optional references in your C# Unity code.

Background

About a decade ago, programming languages pretty much had one way of indicating that a reference variable had no value: it would be set to null (or null-pointer). This works just fine in simple cases but can be fundamentally error-prone, especially when working with values that are meant to sometimes be null, i.e. are optional. If any reference can be null, then do we have to check every value against null before using it? That's far too cumbersome. What if we want to return an optional value, but the caller receiving that value doesn't know the value can validly be null? An inevitable NullPointerException! Documentation can help, but is often not read or not readily available if calling a pre-compiled library.

To address this issue, languages began including solutions in new language (or standard library) versions.

  • Java added Optional in Java 1.8

  • C++ added std::optional in C++17

  • Newer languages like Rust opt to not support null references at all, and use an Option enum (i.e. discriminated union).

C# addressed this problem in its own way in C#8.0, by embracing nullability as a first-class concept with its own syntax. Enable nullable in your file or project, and then use syntax like string? optionalVar; and optionalVar?.method(). The compiler will give some warnings or errors if you attempt to use a nullable reference without checking if it's null first.

The Problem with Nullable in Unity

While I'm not a huge fan of the Nullable approach in general, it poses some big, specific problems in the context of Unity. This thread has some great discussion for some of the reasons why. To stay succinct, we'll focus on the biggest problem with using Nullable for intentionally optional variables:

In Unity, comparing to null on any Unity.Object subclass (ex. GameObjects, Monobehaviours, ScriptableObjects) is non-trivial, sometimes unintuitive, and is rarely what you want for an optional type. Because Unity Objects are C# classes backed by C++ classes, an object can register as null because the backing GameObject has been destroyed, even if the C# class is still referenced. Additionally, calling out to native code to make this check can be costly if made in an Update loop. Read this Resharper warning for a good summary.

Furthermore, much of the convenience syntax for nullability such as the null-coalescing operator ??=, and null-conditional operator .? bypass these lifetime checks, possibly leading your code to behave as though a destroyed GameObject still exists (just because its C# representation hasn't yet been garbage collected).

All this makes nullability a bad fit for optional variables in your Unity code. But there's an easy, better alternative.

The Solution: Option

There is a great open-source library for formally representing optional types in C# called, well, Optional. The project is lightweight and MIT Licensed, meaning there are no concerns with including it in personal or commercial products.

It is easy enough to add to your project from source, or simply add the Nuget for Unity package to your project, and install it via Nuget. The GitHub page can tell you all you need to know, but the simple gist is this:

A simple Option wrapper that holds any object, and can represent an empty or full state:

Option<GameObject> optionalDeathRay = Option.None<GameObject>();
...
// Operate on our death ray with no fear of NullPointerException
optionalDeathRay.MatchSome(deathRay => deathRay.transform.position = ...);
...
if (deathRayTime) {
  // Supply our Option with value any time.
  optionalDeathRay = Option.Some(new GameObject("DeathRay"));
}

It's simple, concise, and completely bypasses any need to check for null which might have unintended results or performance implications. It forces you to explicitly declare when a component may not be there by design, and check if it is.

If there's anything the library doesn't include you can always add it with C# extensions! I like tacking on a TryGet method that I can use just like with Dictionaries.

public static class OptionExtensions {
 public static bool TryGet<T>(this Option<T> option, out T result) {
    if (option.HasValue) {
      result = option.ValueOrFailure();
      return true;
    }
    result = default(T);
    return false;
  }
}

Summary

  • Nullability is an overloaded concept in Unity, and best to avoid.

  • Using Option for your optional types will save you bugs and performance.