This is a story of NullReferenceException part 2. If you haven’t read the part one, you can still do it here.
Don’t trust GetComponent()
GetComponent() is a commonly used function to access current GameObject’s component. Here’s a code snippet showing how to disable object’s renderer, so it is no longer visible in the game view.
var renderer = GetComponent<Renderer>(); renderer.enabled = false;
Of course GetComponent() will return null if current object doesn’t have the requested component. That is a serious risk, because components can be easily removed by the designer at any time without any warning.
Use [RequireComponent]
First you should use [RequireComponent] annotation whenever it is possible.
[RequireComponent(typeof(Renderer))] public class Player : MonoBehaviour { void Start() { var renderer = GetComponent<Renderer>(); renderer.enabled = false; } }
Note that [RequireComponent] will not guarantee that given component is always available. What it actually does, is adding missing component during object setup. Nothing fancy, but it lowers the risk of NullReferenceException a little.
Decide what to do on failure
Yes, you definitely should do a null check. But you have to decide what you want to do if the component happens to be missing. You have three options in general.
- Display an error and ignore.
- Report the incident as bug.
- Try to recover.
As you can see there’s no option for throwing an exception. That’s because we want our game to be as stable as possible.
1. Display an error and ignore
This is the easiest way to handle these kind of bugs. If you encounter them when working in Unity Editor then you will have a good opportunity to fix it before it is even released. Yet your situation will not be that comfortable if this issue appears on your player’s devices that usually you don’t have direct access to.
[RequireComponent(typeof(Renderer))] public class Player : MonoBehaviour { void Start() { var renderer = GetComponent<Renderer>(); if (renderer) { renderer.enabled = false; } else { Debug.LogError("Missing Renderer component", this); } } }
Please note that on line 7 I wrote if (renderer) instead of if (renderer != null). That’s because the UnityEngine.Object is overriding the (bool) operator and under the hood this is a nice shortcut to do a null check.
2. Report incident as bug
If you have the opportunity, you may want to report this incident as a bug. Automatic bug reporting helps you to fix bugs in production, so you should seriously consider this kind of implementation. Of course you have to ask your player for a permission to send a bug report, because otherwise it may violate the agreement with Google Play or App Store and as a result your application may get banned.
I am personally using a script called SRDebugger from the Asset Store. It allows to display an overlay form that enables sending a bug report to my e-mail address.
[RequireComponent(typeof(Renderer))] public class Player : MonoBehaviour { void Start() { var renderer = GetComponent<Renderer>(); if (renderer) { renderer.enabled = false; } else { SRDebug.Instance.ShowBugReportSheet( null, true, "Missing Renderer Component"); } } }
Alternatives:
3. Try to recover
This is the third option and the only one that allows your game to work properly. I believe it should never be used, because if there’s a smoke then there’s a fire. Recovering is only hiding the smoke while you should start looking for the fire.
[RequireComponent(typeof(Renderer))] public class Player : MonoBehaviour { void Start() { var renderer = GetComponent<Renderer>(); if (!renderer) { renderer = gameObject.AddComponent<Renderer>(); Debug.LogWarning("Creating missing component Renderer"); } renderer.enabled = false; } }
Assert GetComponent()
Remember that you should look for any issues as soon as possible. If your script is using a component sometime in the future, make sure to check at the beginning if this component is available.
[RequireComponent(typeof(Renderer))] public class Player : MonoBehaviour { private Renderer _renderer; void OnEnable() { _renderer = GetComponent<Renderer>(); Assert.IsNotNull(_renderer, "Missing Renderer Component"); } public void Hide() { _renderer.enabled = false; } }
Watch out for callbacks!
If your game includes callbacks that are called from external code and/or from another thread you may find yourself in a situation, when NullReferenceException is thrown out of nowhere. It can happen with Social API when callback may be invoked seconds after making a request. And at that time the object that made a request may… no longer exist.
OK, that’s not really true, but we can run into fake null issue here that we were talking about before. Let’s consider a scenario like this:
- Results scene is loaded.
- ResultsScreen script is requesting Social API for current player’s score.
- The player clicks on “main menu” button.
- Results scene is unloaded.
- Main menu scene is loaded.
- Social API callback is being invoked inside ResultsScreen script.
So if Results scene is unloaded, all of its objects should be destroyed at the time of executing sixth step. That’s not true, because the reference to ResultsScreen object is still valid and object exists, but it reports itself as null by faking its own null check as I explained in the previous article. It may look like this:
public class ResultScreen : MonoBehaviour { public Text ScoreLabel; void Start() { Social.LoadScores("Leaderboard01", scores => { if (scores.Length > 0) { ScoreLabel.text = scores[0].formattedValue; } }); } }
Can you guess where you will receive a NullReferenceException? On line 10 and it really doesn’t matter if ScoreLabel has been assigned or not. You won’t be able to access it at this point.
How to protect yourself from this kind of situations? The answer is strange but simple. You should do a null-check of this.
public class ResultScreen : MonoBehaviour { public Text ScoreLabel; void Start() { Social.LoadScores("Leaderboard01", scores => { if (!this) { Debug.LogWarning("Current object no longer usable. Discarding callback."); return; } if (scores.Length > 0) { ScoreLabel.text = scores[0].formattedValue; } }); } }
Yes, that’s right! Current object won’t be null because it simply cannot be, but since Unity is faking null checks, this is the best way to check if the current object can be still used!
Of course you can null-check all your objects that you’re about to use, but in more complicated scenarios this may be simply too much. Checking this for null should be good enough.
Keep calm and singletons
Singletons are often perceived as anti-pattern. Still singletons are widely used because of the simplicity and ease of implementation.
In the Unity most common type of singletons are:
- Singletons created on demand
- Singletons created by the engine (derived from MonoBehaviour)
Let’s look at the second type of singleton.
class Singleton : MonoBehaviour { public static Singleton Instance { get; private set; } void Awake() { Instance = this; } }
First, you should make sure that singleton Instance field is assigned as soon as possible, so I’m assigning it in Awake() method. But that’s not enough. There’s a serious risk that Awake() will not be called even once. This risk is caused by the assumption that Singleton object exists on the scene.
Knowing that, you have to create an assert in each class, where you intend to use this singleton:
class MyScript : MonoBehaviour { void Start() { Assert.IsNotNull(Singleton.Instance, "Missing Singleton!"); } }
Note that I’m assigning a singleton in Awake() function, but I’m testing it for null in Start() function instead of OnEnable() function.
That’s because Awake() and OnEnable() functions are called one after another for every enabled GameObject. It’s not explained too well in the official documentation, so I will try to clarify that.
There are scripts: ScriptA and ScriptB both with Awake(), OnEnable(), and Start() functions. The call order may look like this:
- ScriptA: Awake()
- ScriptA: OnEnable()
- ScriptB: Awake()
- ScriptB: OnEnable()
- ScriptA: Start()
- ScriptB: Start()
So as you can see, you cannot be sure that when you’re in OnEnable() that all singletons are assigned. The risk is even greater that sometimes singleton may be assigned, and sometimes not!
You can force your script to be initialized before anything else using Script Execution Order, but to be completely safe, don’t use singletons in Awake() and OnEnable().
Suggestions?
Do you have any other interesting suggestions on how to make your project robust by securing your code against NullReferenceExceptions? Please share your suggestions using the comment form below! We will appreciate it!