Null static reference does not give NullReferenceException

I recently came across a game crash that took me a while to pinpoint. Our code had a singleton instantiated like this…

public class MyClass : MonoBehaviour {
    static public MyClass Instance = null;

    private void Start() {
        Instance = this;
    }
}

The MyClass.Instance static reference was being called before MyClass.Start() had a chance to set that variable, and the game would crash on launch. (I’ve since fixed the bug, and Instance is now a property that gets set just-in-time in the get; method).

Normally I would have expected a NullReferenceException error in the console, which would have led me to these lines almost immediately. Instead, the game crashed silently, and it took me a long time to trace it back here. My question isn’t so much how to fix the bug, but rather: should I not expect an NRE in these cases? Since Instance is a static member, is it just a particular C# quirk that circumvents throwing an NRE?

But isn’t the “crash” a NRE because you access it when it’s NULL? You only get that exception when you try to use a NULL.

Doing it JIT in a getter is certainly a more secure method but also it’s important to note that you should always try to use “Awake” because they all happen before any “Start” methods are called. This means initialisation that doesn’t refer to anything outside of the class should be done there. Then, when “Start” is called, you know all the “Awake” have been called.

See: Unity - Manual: Execution Order of Event Functions

Some things to think about:

  1. your class is NOT a MonoBehaviour, so it would never have the private Start() method called.

  2. if it WAS a MonoBehaviour, then Start() is actually called extremely LATE in the cycle, as @MelvMay points out with the timing diagram

  3. the above is NOT a singleton… it’s just … some code. Here are some notes on the half dozen or so primary ways of making “singletons,” for various definitions of singletons and lifecycles, under Unity.

There’s a lot of flavors and they all have very different behaviors:

ULTRA-simple static solution to a GameManager:

OR for a more-complex “lives as a MonoBehaviour or ScriptableObject” solution…

Simple Singleton (UnitySingleton):

Some super-simple Singleton examples to take and modify:

Simple Unity3D Singleton (no predefined data):

Unity3D Singleton with a Prefab (or a ScriptableObject) used for predefined data:

These are pure-code solutions, do not put anything into any scene, just access it via .Instance!

If it is a GameManager, when the game is over, make a function in that singleton that Destroys itself so the next time you access it you get a fresh one, something like:

public void DestroyThyself()
{
   Destroy(gameObject);
   Instance = null;    // because destroy doesn't happen until end of frame
}

There are also lots of Youtube tutorials on the concepts involved in making a suitable GameManager, which obviously depends a lot on what your game might need.

OR just make a custom ScriptableObject that has the shared fields you want for the duration of many scenes, and drag references to that one ScriptableObject instance into everything that needs it. It scales up to a certain point.

And if you really insist on barebones C# singleton, here’s a Highlander:

And finally there’s always just a simple “static locator” pattern you can use on MonoBehaviour-derived classes, just to give global access to them during their lifecycle. WARNING: this does NOT control their uniqueness!

public static MyClass Instance { get; private set; }

void OnEnable()
{
  Instance = this;
}
void OnDisable()
{
  Instance = null;     // keep everybody honest when we're not around
}

So Kurt-Dekker you’re right, the code as written wasn’t a MonoBehaviour – I wrote it out freehand & just neglected that bit. I’ve edited the snippet to correct that.

Both you and MelvMay are correct, Awake() is guaranteed to be called first – that was another part of my bug fix: both moving the assignment into Awake() and allowing a JIT fallback.

I’m not interested in the definition of singletons or how to implement a GameManager class; that was not my question.

My question is why, when the static Instance member is null, and another object tries to access it, I don’t see an NRE in the console. As you said MelvMay, the crash is certainly caused by trying to access a null reference. However, in most cases (that I know of!) this is actually reflected in the console: “NullReferenceException: Object reference not set to an instance of an object” and so on, with a stack trace that makes it easy to find the problematic line.

In this case however, there was nothing in the console or error logs, and I’m wondering why that is.

2 Likes

If I had to guess it is because the null reference actually happened at static constructor time, which is probably a long, long time before Unity gets its log system up and running, shaves its face, combs its hair and becomes ready for business.

2 Likes

Although I’m not an expert in this area, Static initialisation can be a beast unto itself. It’s a bit like doing bad things inside C++ constructors, good luck finding some of them! :slight_smile:

There be Dragons in Static initialisation! :slight_smile:

2 Likes

Anyone here who can explain what’s happening? I do have a similar situation and it’s really bad as it hides bugs and will obviously result in very hard to track issues at an arbitrary later point in time.
In my situation it’s not realated to object initialization order at all as the object with the attached MonoBehaviour doesn’t even exist and, the usage is done by the gamer, pressing a button.

Please create your own thread then. A NRE when testing in the editor certainly will not crash the game / editor. When you talk about a build of your game, it highly depends on what platform and if exception handling has been stripped for performance.

Do not try to hijack this thread with your problem. Create your own thread and be much more specific about your problem. The OP of this thread also didn’t provide any useful information, not even if it was an actual crash (check the log file) or if the application just hangs. If it hangs there may be other code that is responsible for that. We need much most details to be of any help.

As Bunny83 said please create a new thread instead.
Thread locked

1 Like