Null Check and Equality in Unity

In some programming languages – like C# – it is a common practice to use comparison operators and functions to check for null references. However, when programming in Unity, there are some particularities to keep in mind that the usual C# programmer usually does not take into consideration. This article is a guide on how these caveats work and how to properly use C#’s equality tools in Unity.

A quick recap of C#’s equality functions and operators

There are three main ways to check for equality in C#: the ReferenceEquals function, the == operator and the Equals function. If you are an experienced C# developer that knows your ways in and out of the language’s equality tools, fell free to skip this section and jump straight to the Unity section.

The ReferenceEquals function

This function is not as famous as the other alternatives, but it is the easier to understand. It’s a static function from the Object class and it takes two object arguments to be compared for equality.

public static bool ReferenceEquals (object objA, object objB);

It returns a bool that represents whether the two arguments have the same reference – that is, the same memory address. It can not be overwritten, which is understandable. It does not check for the object contents and/or data, it only takes their references into account.

The == operator

The == operator can be used for both value and reference types. For built-in value types, it returns whether the values are the same. For user-defined types, they can only be used if the operator has been defined. Here’s an example of a == operator defined for the Coordinates struct. The != operator must also be defined whenever the == is, otherwise a “The operator == requires a matching operator ‘!=’ to also be defined” compilation error will be thrown.

public struct Coordinates
{
    private int _x;
    private int _y;

    public static bool operator ==(Coordinates a, Coordinates b)
    {
        return a._x == b._x && a._y == b._y;
    }

    public static bool operator !=(Coordinates a, Coordinates b)
    {
        return !(a == b);
    }
}

The operator’s behaviour differs a bit for user-defined reference types (a.k.a. objects). A custom == operator can be defined for any reference type, but unlike for value types, you don’t have to define the operator before using it. The reason behind that is because the SystemObject class (which all other reference types inherit from) implements the == operator. The implementation is really simple, and well known: two Objects are considered equal if their references (i.e. their memory addresses) are the same. Its behaviour is the same as the ReferenceEquals function explained above.

Although this might make sense, sometimes we want to implement a custom behaviour for this operator, usually when we want 2 different objects (with different references) to be considered equal if some of their data is the same. Consider the following example with the Person class, where two instances are equals (according to the == operator) if they share the same _id.

public class Person
{
    private string _name;
    private int _id;
    
    public static bool operator ==(Person a, Person b)
    {
        if (ReferenceEquals(a, null) || ReferenceEquals(b, null))
            return false;
        return a._id == b._id;
    }

    public static bool operator !=(Person a, Person b)
    {
        return !(a == b);
    }
}

Not that both arguments are of type Coordinates, so the operator can only be used on objects of that type – and on its subtypes.

The Equals functiom

This function lives in the Object class but unlike ReferenceEquals, it is virtual and can be overwritten by any user-defined type. Its default behaviours for reference types (implemented in the Object class) mimics ReferenceEquals: it checks if the object share the same reference. Its default behaviour for value types (defined in the ValueType class) checks if all fields of both objects are the same. Check its definition below.

public virtual bool Equals (object obj);

Unlike the == operator, it is not static and it only takes 1 parameter of type object which representes the object to check equality against. Also notice that unlike the == operator, the argument is of type object, and not of the same type as we are implementing Equals for. Check the example below, where the function is implemented in the Coordinates class.

public class Coordinates
{

    private int _x;
    private int _y;
    
    public override bool Equals(object obj)
    {
        if (ReferenceEquals(obj, null))
            return false;
        if (obj is Coordinates c)
            return c._x == _x && c._y == _y;
        return false;
    }
}

In addition to checking the parameter for a null reference, it is necessary to cast it into Coordinates before actually checking for equality. It is also worth noting that the == operator can check both parameters for null values, while Equals only checks its only parameter. If the object we are calling Equals on is null, a NullReferenceException will be thrown.

If you want to dive deeper into C#’s equality tools, you might want to check this article out.

Equality in Unity

Out of the three main equality tools C# provides (ReferenceEquals, Equals and ==), only the == operator requires special attention – the other two behave exactly like they do in vanilla C#.

Unity provides a custom implementation of the == operator (and naturally for != as well) for types that inherit from the UnityEngine.Object class (e.g. MonoBehaviour and ScriptableObject). For other types – like a custom class that doesn’t inherit from any other class – C#’s standard implementation will be used. When comparing a UnityEngine.Object against null, the engine not only checks if the operand it null by itself, but it also checks if its underlying entity was destroyed. For example, observe the following sequence of actions:

Assuming we have a MonoBehaviour called ExampleBehaviour, create a new GameObject and attach an instance to it:

var obj = new GameObject("MyGameObject");
var example = obj.AddComponent();

Later on the game, we decide to destroy the ExampleBehaviour‘s instance:

Destroy(example);

And later on, we check the ExampleBehaviour instance for equality against null:

Debug.Log(example == null);

The debug statement above will print “true“. At first, that might seem obvious because we just destroyed that instance, but as I explained on my previous article, the instance’s reference is not null and it was not garbage-collected yet. In fact, it won’t be garbage-collected until the scope it has been defined still exists. What Unity’s custom == operator does in this scenario is to check if the underlying entity has been destroyed, which in this case is true. This behaviour helps programmers identifying objects that have been destroyed but still hold a valid reference.

Other similar operators

A few C# operators have implicit null checks. They are worth investigating here because they behave inconsistently with the == operator.

The null-conditional operators ?. and ?[]

These operators were planned as shortcuts for safe member and element access, respectively. The portion of code following the ?. or ?[] will only be executed if the object they been invoked on is not null. In standard C#, they are the equivalent of executing a similar call wrapped in a null check. For example, the following code, assuming that _dog is not instance of UnityEngine.Object:

if (_dog != null)
    _dog.Bark();

Can be replaced with:

_dog?.Bark();

Although these two code snippets might behave exactly the same in vanilla C#, they behave differently in Unity if _dog is an instance of UnityEngine.Object. Unlike ==, the engine does not have custom implementation for these operators. As a consequence, the first code snippet would check for underlying object destruction whereas the second code snippet would not. If you use the Rider IDE, the warning “Possible unintended bypass of lifetime check of underlying Unity engine object” will be displayed whenever one of these operators are used on a object of a class that inherits from UnityEngine.Object.

The null-coalescing operators ?? and ??=

The ?? operator checks if its left operand is null. If it not is, it returns its left operand. If it is, it returns its right operand. In the example below, assuming that Animal is a class that does not inherit from UnityEngine.Object, a3 will point to a2 because the left operand of ?? (a1) is null.

Animal a1 = null;
Animal a2 = new Animal();
Animal a3 = a1 ?? a2;

It is equivalent to

if (a1 == null)
    a3 = a2;
else
    a3 = a1;

The ??= is an assignment operator that assigns its right operand to its left operand only if its left operand is null. In the example below, a1 will be assigned to a3 only if a1 is null.

Animal a1 = ...
a1 ??= a3;

It is equivalent to

Animal a1 = ...
if (a1 == null)
    a1 = a3

Just like the null-conditional operators, there are no custom implementations of these operators for UnityEngine.Object. As a consequence, if the Animal class from the code snippets above inherited from MonoBehaviour, for example, the implicit null checks would not behave like the null checks using the == operator. Thus, their respective “equivalent” code would not be equivalent anymore. Again, a warning will be displayed in the Rider IDE when using these operands on objects that inherit from UnityEngine.Object.

Wrapping up

Equality operators and functions are basic language constructs present in every C# programmer’s toolset. When developing in standard C#, a programmer should keep in mind how some of these constructs behave differently for value and reference types. When programming in C# for Unity, a developer must also keep in mind how the engine tailored the language’s == and != operators to its ecosystem. In addition, one must keep in mind that some shortcut operators that perform implicit null checks behave inconsistently with the engine’s == operator. With that in mind, a developer should master all these equality tools in order to avoid undesired behaviour. Finally, some IDEs like Rider will warn the programmer about possible pitfalls regarding these operators.

That’s it for today. As always, feel free to leave a comment with questions, corrections, criticism or anything else that you want to add. See you next time!

Source

Passion for Coding: .NET == and .Equals()
Unity Blog: Custom == operator, should we keep it?
Rider: Avoid null comparisons against UnityEngine.Object subclasses
Rider: Possible unintended bypass of lifetime check of underlying Unity engine object

Unity’s Scripting Duality and Object Destruction

The Unity engine provides users with tools and abstractions that ease its usage and hide its complexity. Although we often take these commodities for granted and completely forget they exist, we often face a situation in which they become apparent, usually due to an unexpected behaviour. In this article, I discuss how an example of such an abstraction tool — Unity’s scripting solution — can accidentally expose the engine’s underlying mechanisms.

The duality: managed vs. native

As we all know, Unity’s programming language of choice is C#, but we need to keep in mind that the engine code itself isn’t written in C#, but in C/C++. Consequently, every code piece that invokes engine code (e.g. transform, GetComponent, gameObject.SetActive) does not run directly on the C# side, but on the native C++ side instead. An object of a type that inherits from UnityEngine.Object (like a MonoBehaviour) has two counterparts that live in different worlds: a managed object in C# world and an object in the native engine world. These two entities are linked to each other — the managed entity holds a pointer to the native entity — but are not, in fact, the same thing. Calls to the managed entity (often referred as a “wrapper”) will be transferred to the native entity whenever necessary: when a call to engine code is invoked. On the opposite side, wrapper calls that do not invoke engine code will not send to the native entity and will execute locally.

This might seem an unnecessary deep dive into the engine, but it brings some unexpected consequences. Take the example below. A Dog is a MonoBehaviour that does two simple things: Bark and Move. The first is a pure C# method that does not invoke engine code whereas the second moves the dog’s GameObject, invoking native engine code.

public class Dog : MonoBehaviour
{
    public void Bark()
    {
        Debug.Log("Woof!");
    }

    public void Move()
    {
        transform.position += new Vector3(1f,1f,1f);
    }
}

Now let’s test our Dog script with the help of the DogExample script below. This script creates a dog on Awake and can perform three actions based on user input: destroy the dog, make it bark and move. Simple as that.

public class DogExample : MonoBehaviour
{
    private Dog _dog;

    private void Awake()
    {
        var dogGameObject = new GameObject("Dog");
        _dog = dogGameObject.AddComponent();
    }

    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.D))
            Destroy(_dog);
        if (Input.GetKeyDown(KeyCode.B))
            _dog.Bark();
        if (Input.GetKeyDown(KeyCode.M))
            _dog.Move();    
    }
}

Following, let’s test it, creating a new empty scene with the above script attached to a GameObject. Once we play the game, a new GameObject named “Dog” is created, with a Dog script added to it. If we hit the B key, the dog barks (in Unity’s console). If we hit the M key, it moves (we can see its position in the inspector). Then, we hit the D key to destroy that Dog instance. In the editor hierarchy, we can still see the “Dog” object, but there is no Dog script attached to it. If we try to make the dog move, a MissingReferenceException is thrown, stating that “The object of type ‘Dog’ has been destroyed but you are still trying to access it”. The exception makes sense because we’ve just destroyed the Dog instance. The same thing happens if we decide to destroy the dog.gameObject instead (but then the “Dog” game object would be removed from the hierarchy).

However, if we press the B key to make the dog bark, no errors are thrown and the “Woof!” message is displayed on the console. What just happened? The Dog script was just destroyed and an error was thrown proving it. How can the dog still bark?

It is here where the managed vs. native duality explained in the previous session becomes useful. The two entities are different: a Dog script lives in the managed C# world and its underlying components (i.e. its GameObject and Transform) live in the native, C++ engine world. They are somewhat connected, but they are not the same thing. When the wrapper dog was destroyed, its script instance was removed from the “Dog” game object and its native components were wiped out, but the managed Dog script wasn’t, and it will only be destroyed when it gets garbage-collected. As long as we keep a reference to that Dog instance, it will live. When we invoke the dog’s Bark method, we are simply invoking a regular C# method of an entity that lives in the managed world. As long as it doesn’t try to access any of its native world entities that just got destroyed, that call method should execute successfully.

This scenario changes if we try to access any of its underlying native entities, like its gameObject property, as we do in the Move method. In that case, the engine throws an error to let us know that the object of type Dog we are trying to access was destroyed. Note that the exception is a MissingReferenceException, an exception defined in the UnityEngine namespace. It is not a NullReferenceException and thus, it is not a C# built-in exception. Again, that sounds too deep of a dive into the engine details, but this subtle detail underscores the fact that the managed entity (the Dog instance) was not destroyed at all, otherwise an NullReferenceException exception would have been thrown. In fact, the wrapper object still lives. The call to Destroy will only destroy native, engine components. In other words, the lifetime of native entities will be determined by a call to Destroy (or a non-additive scene load) whereas the lifetime of managed entities will be determined by the garbage collector. Thus, the two entities have different life cycles, and sometimes we need to keep that in mind.

For a deeper look into how the Unity engine works under the hood, check this article out.

The potential problem

We just went through why user-defined MonoBehaviours still live in the managed world even after their corresponding native entity was destroyed. Also, we discussed how we can still invoke some of their methods successfully, as long as they don’t try to access their native engine components. This fact introduces two important questions: how do we easily identify that these managed entities should not be accessed anymore because their underlying entities were destroyed? Is it a good practice to still access a MonoBehaviour after is has been destroyed?

Let’s start answering the second question: no, it is not a good idea to access a MonoBehaviour after its native entities were destroyed. One might think that it’s safe to keep some of its methods free from accesses to native, engine code, but this practice hurts code maintainability badly. It introduces an unspoken (or undocumented) rule that some methods should not try to access some specific components. Consequently, some developer — unaware of this weird rule — might change the code down the development process, which could potentially introduce errors. Additionally, it goes against the idea of using a MonoBehaviour, which is to attach scripts to GameObjects so they can interact. If you want to use a objects that does not necessarily relate to a GameObjects and its life cycle, do not inherit from MonoBehaviour and use a vanilla C# class instead — or maybe you want to use a ScriptableObject.

The solution

The first question remains open: how do we easily identify that these managed entities should not be accessed anymore? The answer comes from Unity. We can easily check if the underlying entities of a MonoBehaviour were destroyed in two ways:

  • Check for equality against null:
     
    if (_dog == null)
        _dog.Move();
    
  • Check it as a boolean expression:
     if (_dog == true)
        _dog.Move();
    

    or simply

     if (_dog)
        _dog.Move();
    
  • The UnityEngine.Object class (which MonoBehaviour inherits from) implements custom equality operators that check if the underlying entities were destroyed. This operation is more complex than simply checking if the object reference is null because it invokes native code to check if the underlying entity was destroyed. As a consequence, it is also less performant than a vanilla C# null comparison. That’s why the Rider IDE displays a warning (“Comparison to ‘null’ is expensive”) whenever this null check is performed in a performance critical context. This custom implementation was reconsidered a while ago by Unity developers, but it was kept and still exists. This tool gives us a way to safely and easily check the lifetime of a MonoBehaviour‘s underlying object, which was exactly what we were looking for. But there is one thing to keep in mind…

    One little trap

    Even though we can use Unity’s custom equality operators to check whether a MonoBehaviour has been destroyed, we need to be careful about when we perform this check. As it happens, Unity does not destroy an object exactly when Destroy in invoked. Instead, the given object is tagged for destruction, which will only happen after the end of the current Update loop, but before rendering. At that point, all objects that were tagged for destruction will be actually destroyed. As a consequence, a check for destruction invoked right after (within the same Update loop) the Destroy call will return false. For example:

     
    private void DestructionTest()
    {
        Destroy(_dog);
        Debug.Log($"Is the dog null/destroyed? {_dog == null}");
    }
    

    The method above, when executed outputs “Is the dog null/destroyed? False”. If we run the same check one frame later, or even inside LateUpdate, the check returns true. That happens because the call to Destroy doesn’t actually destroy the object, it only tags it for later destruction. After the Update loop, the engine gathers all objects tagged for destruction and actually destroys them, one by one. As a consequence, we need to keep the delayed destruction in mind when checking for existing entities.

    Not all C#’s equality operators implement this custom behaviour. For a deeper look into Unity’s equality operators, check this article on the subject.

    Conclusion

    Unity does a great job at hiding implementation details and at abstracting away the complexity of its native side by providing C# wrappers for developers. Although this abstraction layer can be often ignored, there are some nuances we should keep in mind, like the different lifetimes of managed and native entities. But once we understand what is going on behind the scenes, it becomes clear that some unexpected behaviours are just consequences of Unity’s scripting duality.

    In this article, we learned how managed entities that inherit from UnityEngine.Object have a native counterpart and how their lifetime differ. We also learned how to use Unity’s custom implementation of equality operators to safely check for object destruction. Finally, we learned how the engine’s strategy to handle destruction can interfere on the lifetime check we just mentioned.

    That’s it for today. As always, feel free to leave a comment with questions, corrections, criticism or anything else that you want to add. See you next time!

    Source

    Unity Blog: Custom == operator, should we keep it?
    Rider: Avoid null comparisons against UnityEngine.Object subclasses
    Rider: Possible unintended bypass of lifetime check of underlying Unity engine object

    C#’s Finalizer/Destructor Trap

    Let’s talk about C#’s finalizers (also called destructors in C#) and how a common mistake when using them might lead to unwanted behaviour, especially in applications made with the Unity engine.

    Finalizers

    A finalizer is a method that is called whenever an instance of a class is being garbage-collected. It is used for cleanup, commonly to release resources. The code below contains an example class for a music player, where its destructor closes an open file. Note that finalizers always start with a tilde and can’t have any parameters.

    public class MusicPlayer
    {
        private FileStream _file;
    
        public void Play(string filePath)
        {
            _file = File.Open(filePath, FileMode.Open);
            // ...
        }
    
        ~MusicPlayer()
        {
            if (_file != null)
                _file.Close();
        }
    }
    

    Disclaimer: even though destructors and finalizers are two different things, C#’s spec treats them as the same. If you’re acquainted with both definitions, the C#’s mechanism discussed in this article is actually a finalizer. Also, if you come from a C++ background, even though C#’s syntax for finalizers resembles C++’s syntax, keep in mind that they’re not the same. C++ destructors are called explicitly (and deterministically) by the user whereas C#’s finalizers are called implicitly (and nondeterministically) by the garbage collector.

    A quick intro to Unity’s MonoBehaviour

    The Unity engine provides a handy base class (MonoBehaviour) that contains common behaviour often needed for game development. Among other things, it contain event methods that are automatically called by the engine under given scenarios. These event methods are guaranteed to be called under the right circumstances and therefore developers can rely on them. Some of these methods are called when an object is created and when it’s destroyed, analogous to constructors and finalizers (which should not exist for MonoBehaviours). For example, the Awake methods is called when an object is created and the OnDestroy method is called when it’s destroyed.

    Common practice

    It is common practice to use Awake and OnDestroy to subscribe and unsubscribe to events, respectively. The code below shows an example.

    The ExampleButton behaviour contains only an event, for example purposes:

    public class ExampleButton : MonoBehaviour
    {
        public event Action OnClick;
    }
    

    The MyBehaviour behaviour subscribes to _button‘s OnClick event when it’s created and unsubscribes to the same event when it’s destroyed.

    public class MyBehaviour : MonoBehaviour
    {
        [SerializeField] private ExampleButton _button;
    
        private void Awake()
        {
            _button.OnClick += Foo;
        }
    
        private void OnDestroy()
        {
            _button.OnClick -= Foo;
        }
    
        private void Foo()
        {
            // ...
        }
    }
    

    The code above works as expected and both initialization and cleanup execute as expected.

    The naive thought

    At some point during development, we introduce a regular, non-MonoBehaviour class called ExampleClass. Unlike MonoBehaviours, it can’t rely on event methods like Awake for initialization. As an alternative, we normally use the class’ constructor. Analogously, ExampleClass can’t rely on event methods like OnDestroy for cleanup. As an alternative, we use its finalizer.

    public class ExampleClass
    {
        private string _name;
        private ExampleButton _button;
    
        public ExampleClass(string name, ExampleButton button)
        {
            _name = name;
            _button = button;
            _button.OnClick += Bar;
        }
    
        ~ExampleClass()
        {
            Debug.Log($"Calling {_name} destructor.");
            _button.OnClick -= Bar;
        }
    
        private void Bar()
        {
            // ...
            Debug.Log("Bar");
        }
    
        private static void Something()
        {
            Debug.Log("Something");
        }
    }
    

    Let’s define ExampleBehaviour, a MonoBehaviour that contains an ExampleButton and an ExampleClass. On its Awake method, the _object field (of type ExampleClass) is initialized.

    public class ExampleBehaviour : MonoBehaviour
    {
        [SerializeField] private ExampleButton _button;
    
        private ExampleClass _object;
    
        private void Awake()
        {
            _object = new ExampleClass("Foo", _button);
        }
    }
    

    A new Unity scene is created and only 2 objects are added to it: one containing an ExampleButton (called MyButtonGameObject) and one containing an ExampleBehaviour (called MyBehaviourGameObject). Whenever the scene is played, MyBehaviourGameObject’s Awake method is invoked and the _object variable is assigned, as expected. Inside ExampleClass constructor, the event subscription is executed, as expected. Nothing unusual so far.

    Screenshot 2019-09-08 at 17.33.41

    Then, because of some design decision, the MyBehaviourGameObject object is destroyed along the application lifetime. We realize that we might have to do some cleanup because _object should unsubscribe from _button‘s OnClick event. But then we come to the conclusion that it actually should be alright and that no additional cleanup should be necessary. Whenever MyBehaviourGameObject gets destroyed, the garbage collector will collect its ExampleBehaviour script. Since _object only belongs to MyBehaviourGameObject, it should be collected as well, which should trigger its finalizer and unsubscribe from the events.

    The trap

    Later during development, we notice some weird behaviour whenever the user clicks on MyButtonGameObject: there’s ExampleClass behaviour still being executed. But at that point of the execution, there should be no activeExampleClass in the scene because MyBehaviourGameObject was destroyed! After checking the call stack, we find out that Bar is being called by ExampleButton‘s OnClick event. But wait a second, something is wrong. ExampleClass‘s finalizer was responsible for event unsubscription. What happened? After some more debugging, you finally find out that the object’s finalizer doesn’t ever get called. But why isn’t the finalizer being called at all?

    Maybe the garbage collector is not running, for whatever reason. Let’s try to force collection using a simple script.

    public class ManualGarbageCollector : MonoBehaviour
    {
        private void Update()
        {
            if (Input.GetKeyDown(KeyCode.C))
                GC.Collect();
        }
    }
    

    For testing purposes, I manually delete MyBehaviourGameObject and press the C key. The NullPointerExceptions are still being thrown and the finalizer is still not executing. What is going on? Is the garbage collector broken? MyBehaviourGameObject was destroyed and _object should be collected, which should trigger the event unsubscription.

    To answer those question, we need to understand how C#’s garbage collector works. First, no, it’s not broken at all. In fact, it’s doing exactly what it’s told to. It’s our minds that forgot what we were doing. An object will only get collected whenever there are no references to it. None at all. Zero. If there’s one, even if really hidden, forgotten reference to an object, it will not get collected by the garbage collector. It’s as simple as that. But it still doesn’t make sense. The only reference to _object was inside ExampleBehaviour, right?

    Wrong.

    We forgot about the one reference we were trying to get rid of: the one inside ExampleButton‘s OnClick event. But wait a second, we didn’t store a reference to _object in that event, we just stored a reference to a method, right?

    Again, wrong. Although it looks like we’re subscribing to a method, we need to keep in mind that it’s an instance method. It belongs to an instance of a class, a.k.a. an object. Under the hood, that method’s reference consists – among other things – of a reference to the method in memory and a reference to the instance the methods should be called on. If you ever programmed in Python, think of how the first argument of an instance method is self. As a consequence, an event subscription to an instance method will keep a reference to the instance itself. Therefore, it will stop the object of being collected by the garbage collector.

    We can show show that _object‘s reference is, in fact, being kept by MyButtonGameObject by destroying the latter. Once destroyed, the garbage collector will collect ExampleButton‘s memory and later, _object.

    If Bar was a static method, this wouldn’t happen because references to static methods don’t include a reference to an object. Consequently, _object‘s reference count would drop to 0 and it would be eventually collected by the garbage collector.

    The solution

    Thankfully, there is an easy solution for that: create a cleanup method and explicitly call it whenever necessary. On this article’s example, the perfect candidate would be ExampleBehaviour‘s OnDestroy method.

    On ExampleClass:

    public void Cleanup()
    {
        _button.OnClick -= Bar;
    }
    

    On ExampleBehaviour:

    private void OnDestroy()
    {
        _object.Cleanup();
    }
    

    This fix not only will guarantee that events get unsubscribed but it will also – ironically – remove the last references to _object, which allows its (now obsolete) finalizer to be called.

    The bonus trap

    There’s another trap regarding finalizers that is not related to the one described above. Finalizers are called by the garbage collector, which runs on a separate thread than Unity’s main thread. As a consequence, two problems might come up.

    First – as usual – Unity engine code can not be called from a separate thread. Calling something as simple as _foo.gameObject will throw a UnityEngine.UnityException with the message “get_gameObject can only be called from the main thread“.

    Second, Unity will not catch and log exceptions running on separate threads. Therefore, any exceptions thrown inside finalizers (like the one described above) or in any separate thread might fly under the radar and never get acknowledged by the developers. There are two possible fixes for this problem. One is the obvious: catch the exceptions inside the thread itself. Another one can be used for user-defined threads (and thus is not applicable to GC threads): use Task instead of Thread to start a new thread with exception handling support.

    Conclusion

    Whenever using C# finalizers, keep in mind that they will only get called when no references to their respective objects are left, including references to instance methods. Therefore, using finalizers to unsubscribe from events and to remove delegate references might lead to unwanted behaviour. As an alternative, create a cleanup method and explicitly call it whenever necessary.

    As a good practice, try to use finalizers for what they are good for: freeing resources. For other usages, don’t rely on them and explicitly invoke cleanup methods. Some even say that pure finalizers should avoided and the disposable pattern should be used instead.

    That’s all for today. As usual, feel free to leave a comment with corrections, questions, criticism or compliments. See you next time!