Search Unity

Understanding lifespan of C# classes (non-MonoBehaviour)

Discussion in 'Scripting' started by FeastSC2, Mar 4, 2020.

  1. FeastSC2

    FeastSC2

    Joined:
    Sep 30, 2016
    Posts:
    978
    Many times in my project I avoided making standard C# classes (classes not inheriting from MonoBehaviour) because I have not found a way to easily get rid of them or to let the program know that this class should no longer be used and that it should point to null.

    Maybe it's because I misunderstand something...

    With a MonoBehaviour you can call Destroy() and it will make any references to this object point to null.

    With a standard C# class, you cannot call Destroy() meaning that as long as somewhere in the program there is a reference to the object, it won't get picked up by the garbage collector and it won't point to null.

    This means that should a script check to see if the object still lives with object != null, he will see that it lives.
    If I try and set the object = null, this will merely make the reference pointer point to null. Meaning that if there's another object that points to the object, the object will still live.

    In comparison to Unity's components where you can simply call Destroy(Component) and any scripts attempting to do something with the component will have a nullreference.
    I would like to know how to replicate this behaviour with standard C# classes.

    How can I destroy an instantiated standard C# class?
    Or am I thinking incorrectly about all of this?
     
  2. csofranz

    csofranz

    Joined:
    Apr 29, 2017
    Posts:
    1,556
    Simply set all references to the object to null. Like in Unity, the garbage collector will do its magic once no object references it any more.

    You can also define a destructor that similar to OnDestoy is invoked. In that dstructor, release all memory you have allocated yourself.
     
  3. csofranz

    csofranz

    Joined:
    Apr 29, 2017
    Posts:
    1,556
    With regards to your question "how can I FORCE destuction of an object in C#', the answer is: you can't except when all referencing objects relinquish their reference. This is a central design decision in C#. The assumption is (and I find it quite sane), that objects know how long they want to retain an object, and therefore it makes no sense to destroy it before that point in time when all objects are done. That allows safe programming without null checks at every guard, switch or dereference.
     
  4. ThermalFusion

    ThermalFusion

    Joined:
    May 1, 2011
    Posts:
    906
    Disclaimer: Someone more knowledgeable might come around to correct me, don't take this as gospel.
    This is not the entire truth, on the C# scripting end the object will live on as any old C# object does in wait for the garbage collector to clean it up. On the engine side of things all resources should be destroyed or unloaded.
    What you get if you try and access an Unity object that's been destroyed is a MissingReferenceException, not usually a NullReferenceException which you usually get from null references. There's also the UnassignedReferenceException which you get from inspector visible/serialized that's not been filled.
    You can see these differences in the inspector as well; None/Missing.
    Unity overrides several operators (bool, ==, !=) that make tests like foo==null return true if the object has had Destroy called on them, even if the C# object might still live.

    Regarding your original question, I don't know of another way than to implement something like this yourself, by turning all fields into properties and check some has-been-destroyed flag whenever you access them.
    There are some concepts like Disposable in .Net where you can control when and how garbage collections of a certain object works, but this may be complicating things more than what it's worth and may not be particularly usefull in everyday Unity use.
     
    Bunny83 likes this.
  5. passerbycmc

    passerbycmc

    Joined:
    Feb 12, 2015
    Posts:
    1,741
    this isn't C++, you do not have to worry about manually managing what is in memory. Once all references to a object go out of scope or get nulled the object will be marked for GC and cleaned up in the future.

    if you want to manually close resources or remove things, you may want to look into the IDisposable interface, the Dispose method that it makes you implement and the using statement.

    its a good way to let you generically calling logic for tearing down a object, or in the case of the using statement a way to ensure Dispose properly gets called once you exit a scope.
     
  6. Long2904

    Long2904

    Joined:
    May 13, 2019
    Posts:
    81
    @ThermalFusion Can you explain it more? So when you call Destroy() does it force the garbage collector to free that memory? And does it change other fields that reference it to be null? You said Unity only overrides some operators, so does the actual reference still point to a valid object in memory? Do I really need to call Destroy or just make sure no other object references it is enough? For example, if I have a normal class (doesn't inherit from MonoBehaviour) with a MonoBehaviour property, how can I free it? Does make sure that no other object references it is enough? Or do I still need to explicitly call Destroy on the MonoBehaviour property?
     
  7. bobisgod234

    bobisgod234

    Joined:
    Nov 15, 2016
    Posts:
    1,042
    Destroy() does not force any garbage collection. Destroy() does not know what fields are referencing the class being destroyed, and as such, can't touch those fields. They still point to the now-destroyed object.

    Fields pointing to the destroyed object are technically not null - they continue to point to the same object. Unity fakes a null to make the null check (if thing == null). This can fail to work in some cases e.g. with the ?? operator.

    For the most part, you shouldn't have to concern yourself with freeing up objects like this. If you don't want to use a class anymore, just don't use it, and the garbage collector will take care of it when it is ready.
     
    Bunny83 likes this.
  8. Severos

    Severos

    Joined:
    Oct 2, 2015
    Posts:
    181
    I guess your question is more of a design problem, why would you need to destroy an object while someone still has reference to it? What's the expected behavior if someone tries to invoke a method on destroyed object?
    If you have considered all designs and still MUST use this method, you can implement the Disposable pattern, it won't destroy the object, but you can release all internal data. Or use a proxy object and nullify the internal state, still the proxy object will not be disposed until all references are cleared.
     
  9. Long2904

    Long2904

    Joined:
    May 13, 2019
    Posts:
    81
    @bobisgod234 Just wonder how can Unity fakes a null check if Destroy() doesn't know whether other fields reference it? If Unity doesn't know then it only can fake the check on the object that was passed into Destroy() and any other objects that still reference the memory still fail the null check.
     
  10. Long2904

    Long2904

    Joined:
    May 13, 2019
    Posts:
    81
    @Severos Sorry for not explained the question correctly. I asked does I still need to call Destroy or just make sure there are no references is enough? And what advantage does Destroy has?
     
  11. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,775
    When you get a reference to a Unity object of any kind, you don't have a reference to the object itself, but a kind of placeholder. When you Destroy() the real object, the placeholder reference still exists, and is still referenced by anything that had grabbed it. Now, if you do this:
    Code (csharp):
    1. if (objectThatWasDestroyed != null)
    this code will act as if it's null, even though it's still a non-null reference to that placeholder object. It does this because Unity overrides the == and != operators for Unity classes to check for the thing the placeholder points to rather than the placeholder itself. (This is the difference, by the way, between MissingReferenceException and NullReferenceException)
     
    bobisgod234 likes this.
  12. Long2904

    Long2904

    Joined:
    May 13, 2019
    Posts:
    81
    @StarManta So does Unity uses something like a double-pointer? Because if they do then it sounds slow and cache-unfriendly. Or do they have something like an entity index/id and do some lookup when checking for null? Do you have some documentation of Unity talking about how behind the scenes work or are you just guessing?
     
    Last edited: Jun 11, 2021
  13. VolodymyrBS

    VolodymyrBS

    Joined:
    May 15, 2019
    Posts:
    150
    There are lot of different articles about this.
    One of official-ish is this https://blog.unity.com/technology/custom-operator-should-we-keep-it
    i's mainly about other topic but it describes null behaviour and whats happening under the hood.

    In short:
    Unity it's C++ engine with C# scripting layer. Any object that derives from UnityEngine.Object (MonoBehaviour, ScriptableObject, Material, Renderer, etc) has representation in C++ part. So yes it has some sort of double-pointer. C# object store reference to C++ object because big part of the functionality is implemented in C++
     
    Severos and Bunny83 like this.