I have a C# script that boils down to this:
public class SingletonTest : MonoBehaviour
{
public static SingletonTest Singleton = null;
public int Value = 42;
void Start() {
Singleton = this;
}
}
This runs fine at first, but my issue is that when I edit a script and then click back into the Unity editor/IDE, I get a whole string of NullReferenceException
s for the line SingletonTest.Singleton.Value
in some other class.
I googled around a bit and this editor reload seems to trigger a process called Domain Reloading (see also). Apparently domain reloading also resets all static fields which explains my error message. I tried a few workarounds suggested on that page but none of them work:
using UnityEngine;
public class SingletonTest : MonoBehaviour
{
public static SingletonTest Singleton = null;
public int Value = 42;
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
static void StaticStart()
{
var arr = FindObjectsOfType<SingletonTest>();
Debug.Log($"Len arr: {arr.Length}"); // This is 0! :(
if (arr.Length > 0)
Singleton = arr[0];
}
void Start()
{
Singleton = this;
}
void OnAfterDeserialize()
{
Singleton = this;
}
}
I am able to kind-of get it to work by putting Singleton = this
in the Update()
function but that solution is ugly, and still gives me a NullReferenceException
on the first frame after reload.
(Please don't suggest making the Value
field static, that is just a simplified example, I do need/want a singleton class)
Consider using a private constructor to force the static field to be initialized with a value when the SingletonTest
object is created by the CLR.
Although Unity normally doesn't recommend using constructors with MonoBehvior
because they're supposed to be scripts(among other Mono and Unity Quirks). I found this works great for my use cases for singletons(such as static editor Dictionary<T>
s that hold loaded metadata etc...
public class SingletonTest : MonoBehaviour
{
public static SingletonTest Singleton { get; set; } = null;
public int Value = 42;
protected SingletonTest()
{
Singleton ??= this;
}
}
Alternatively consider avoiding the assumption that the given field/property is never null.
For example:
void Awake()
{
// call method example ↓ (null-coalescing operator)
SingletonTest.Singleton?.Invoke("Something");
// property example
if(SingletonTest.Singleton != null)
{
Debug.Log($"{SingletonTest.Singleton.gameObject.Name}");
}
}