Search Unity

Destroy() doesn't create NullReferenceExeption?

Discussion in 'Scripting' started by User340, Jun 29, 2009.

  1. User340

    User340

    Joined:
    Feb 28, 2007
    Posts:
    3,001
    I've got 2 GOs in my game. One has the Controller script applied and the other has the Score script applied.

    Controller
    Code (csharp):
    1. var s : Score;
    2.  
    3. function Start ()
    4. {
    5.     // s is now null, because i'm destroying it.
    6.     Destroy (s.gameObject);
    7.    
    8.     yield WaitForSeconds (1.0);
    9.    
    10.     // This should throw a NullReferenceExeption.  But it doesn't!  Why!?
    11.     s.points = 20;
    12. }
    Score
    Code (csharp):
    1. var points = 10;
    The Controller destroys the score GO at the start of the game. So when I try to change the score's points variable, I should get a NullReferenceExeption, but I don't get any errors? Any idea why?
     
  2. tomvds

    tomvds

    Joined:
    Oct 10, 2008
    Posts:
    1,028
    First, destroy does not destroy an object immediately. If you want to be rid of it instantly, use DestroyImmediate(...). That is not recommended, though.

    Second, referencing a destroyed object does not generate a NullReferenceException. It destroys the object but the reference will not be set to null. It will throw a MissingReferenceException instead.

    Third, upon calling destroy, it's quite likely that the system performs a StopAllCoroutines(...), meaning the line where you expect the error is never executed.
     
  3. User340

    User340

    Joined:
    Feb 28, 2007
    Posts:
    3,001
    After I set the s.points, I added a debug.log() (to see if it was executing) and it did execute. It just doesn't generate a MissingReferenceExeption, or any other errors.

    Here's my new Controller script:

    Code (csharp):
    1. var s : Score;
    2.  
    3. function Start ()
    4. {
    5.    // s is now null, because i'm destroying it.
    6.    Destroy (s.gameObject);
    7.    
    8.    yield WaitForSeconds (1.0);
    9.    
    10.    // This should throw a MissingReferenceExeption.  But it doesn't!  Why!?
    11.    s.points = 20;
    12.    Debug.Log (s.points);
    13. }
    Here's an example project:
     

    Attached Files:

  4. Dreamora

    Dreamora

    Joined:
    Apr 5, 2008
    Posts:
    26,601
    Part of the answer you ignore that perfectly was given:

    don't forget, you work within a managed environment, there is no guarantee that anything happens immediately (that would be bad for the performance)
    if you want to get a control state that ensures that "destroyed = no longer used", then add s = null; right after the destroy line
     
  5. User340

    User340

    Joined:
    Feb 28, 2007
    Posts:
    3,001
    In the Destroy() docs it says:

    So destroy() should not take any longer than a frame? Correct?

    Destroy docs:
    http://unity3d.com/support/documentation/ScriptReference/Object.Destroy.html

    I set yield WaitForSeconds(10) and it still didn't throw a MissingReferenceExeption. How long does the destroy() function take?
     
  6. Dreamora

    Dreamora

    Joined:
    Apr 5, 2008
    Posts:
    26,601
    Your function call happened in this frame, not in next frame.
    Check it again with the next frames call to it and it will give you an exception

    Also if you just set it to null after destroying (something I would do anyway as that is the regular way to flag objects for garbage collection), it will also give you exceptions
     
  7. User340

    User340

    Joined:
    Feb 28, 2007
    Posts:
    3,001
    I don't understand. Aren't I already doing that in my script above (Controller)? I destroy the score GO and then access it a second later. This is many, many frames later. Can you clarify?

    I just tried it, and you're right. It does give me a NullReferenceExeption. Thanks!

    I just don't understand why calling Destroy() doesn't give a MissingReferenceExeption when trying to access it 10 seconds after. Because the docs say that object destruction happens before rendering the next frame.
     
  8. GargerathSunman

    GargerathSunman

    Joined:
    May 1, 2008
    Posts:
    1,571
    It runs the yield WaitForSeconds(10) on the frame it's destroyed, but the yield is interrupted by the Destroy next frame. As such, no exception is generated.
     
  9. User340

    User340

    Joined:
    Feb 28, 2007
    Posts:
    3,001
    So Destroy() can interrupt yield statements? Ok. Now I made the Score script destroy itself. The yield shouldn't be interrupted now, right? Here are my new scripts:

    Controller
    Code (csharp):
    1. var s : Score;
    2.  
    3. function Start ()
    4. {
    5.    // s destroys itself at the start of the game.
    6.    yield WaitForSeconds (3.0);
    7.    
    8.    // This should throw a MissingReferenceExeption (because s destroyed itself earlier).
    9.    // But it doesn't!  Why!?
    10.    s.points = 20;
    11.    Debug.Log (s.points);
    12. }
    Score
    Code (csharp):
    1. var points = 10;
    2.  
    3. function Awake ()
    4. {
    5.     Destroy (gameObject);
    6. }
    And Unity still doesn't throw a MissingReferenceExeption. Why doesn't it?
     
  10. shawn

    shawn

    Unity Technologies

    Joined:
    Aug 4, 2007
    Posts:
    552
    Destroying the game object does not destroy the instances of any of the attached MonoBehaviours. The garbage collector does the cleaning up of MonoBehaviour instances that were attached to destroyed game objects. The garbage collector only cleans the MonoBehaviour instance if nothing is referencing that class, but you are referencing it in your controller. Setting s to null will remove the MonoBehaviour reference and then the garbage collector will clean up that instance. (or you can call Destroy() on reference s itself to force it to be destroyed)
     
  11. Dreamora

    Dreamora

    Joined:
    Apr 5, 2008
    Posts:
    26,601
    Just set s to null to mark it for "freeing" and to have a clear state post that point.
    As I tried to explain above: You work in a managed environment, there is no guarantee of the point in time and order in which garbage correction will happen. Any kind of explicit control requires that you take that control and do it.

    What it definitely will never be is "remove and expect it to be gone within the same script function" unless that loops forever.
     
  12. User340

    User340

    Joined:
    Feb 28, 2007
    Posts:
    3,001
    @Shawn: Ok, now I'm specifically destroying the Score script, and I still don't get a MissingReferenceExeption. Here's my new Score script:

    Score
    Code (csharp):
    1. var points = 10;
    2.  
    3. function Awake ()
    4. {
    5.     Destroy (this);
    6. }
    This didn't change anything.

    Ok, I always thought that Destroying a GO totally got rid of it, all it's components/monobehaviours, and all of it's children before rendering the next frame. At least, this is what I understand from reading the docs. The docs specifically say that objects always get destroyed before rendering the next frame.

     
  13. Dreamora

    Dreamora

    Joined:
    Apr 5, 2008
    Posts:
    26,601
    that will destroy it for the engine.
    But the object itself remains valid until the .NET GC cleans it.

    As mentioned, use destroy and then set the reference to null, which is common behaviour and good practice.

    No idea why you want to enforce bad practice, lose a lot of time on nothing and not gaining anything but problems and anomalies.


    So to sum it up:

    Code (csharp):
    1. function Start ()
    2. {
    3.    // s is now null, because i'm destroying it.
    4.    Destroy (s.gameObject);
    5.    s = null; // this marks s as free to be collected by the GC in the next collection round and ensures that no other object is able to use it until then
    6.    
    7.    yield WaitForSeconds (1.0);
    8.    
    9.    // This should throw a MissingReferenceExeption.  But it doesn't!  Why!?
    10.    s.points = 20;
    11.    Debug.Log (s.points);
    12. }
     
  14. NathanWarden

    NathanWarden

    Joined:
    Oct 4, 2005
    Posts:
    663
    Daniel,

    I agree with you, I downloaded your project and you can even see in the inspector that the component is missing immediately, yet, a second later it gives the correct score. It should be giving some kind of error (missing component exception) and it always has for me in the past whenever I've destroyed a script instance and tried to access it by the next frame.

    Nathan
     
  15. User340

    User340

    Joined:
    Feb 28, 2007
    Posts:
    3,001
    @Nathan: THANK YOU! That's exactly the problem!
     
  16. Dreamora

    Dreamora

    Joined:
    Apr 5, 2008
    Posts:
    26,601
    Perhaps if you read above it starts to make sense why it is that way.

    Unity is Unity but Unity as the whole rest of your code are handled by the .NET GC, which is what handles NULL or not NULL. Unity can only remove the object from its own control and no longer access it nor update it (-> it vanishes from the editor links).

    If you want it to be gone from the GC as well, null its reference or your reference counter will NEVER reach 0 thus it will never be removed.

    Really what more is required till you just read what has been posted and think about why the provided source makes sense and why yours is just wrong (two people agreeing on a faulty solution doesn't make it more correct)

    No object will be freed as long as you keep a reference to it active.
    Also if you want the object to be freed you have to null all references to it (or to its cycle)
     
  17. NCarter

    NCarter

    Joined:
    Sep 3, 2005
    Posts:
    686
    This is entertaining. I wasn't aware that objects hung around like this, although I suppose it shouldn't really come as a surprise. It's presumably an unavoidable consequence of having a garbage collected language.

    I did some tests myself and found some additional interesting facts. For one thing, if you do this:

    Code (csharp):
    1. Destroy(something);
    2.  
    3. yield;                  // Wait for Destroy to take effect
    4.  
    5. if(something == null)
    6.     Debug.Log("Something is null");
    ...it will tell you that the reference is null even though it's still possible to access its members. This at least means that a null test is a sensible way to ensure that you don't accidentally access an object that is supposed to have been destroyed.

    This is probably why I never noticed this behaviour before. As a rule, whenever I consider it possible for an object to be unexpectedly destroyed, I always do a null test before accessing its members.

    Secondly, accessing any Unity API feature associated with the object will result in a MissingReferenceException:

    Code (csharp):
    1. Destroy(something);
    2.  
    3. yield;
    4.  
    5. // Each of these causes a MissingReferenceException:
    6.  
    7. var c = something.GetComponent(SomethingElse);
    8. var g = something.gameObject;                    
    9. var n = something.name;
    However, you don't get MissingReferenceExceptions for any members that you defined yourself.
     
    neonblitzer likes this.
  18. User340

    User340

    Joined:
    Feb 28, 2007
    Posts:
    3,001
    Thanks everyone.
     
  19. Barbur

    Barbur

    Joined:
    Oct 30, 2009
    Posts:
    160
    And wouldn't be a nice solution for this to have a GameObject check function like Alife(GameObject). The engine knows perfectly if an object is being destroyed or not so that would be a nice solution to avoid using null comparisons. Does this already exist on Unity?

    I have a similar problem. I have a space ship being fired by 2 weapons. Each weapons have the ship as member of its classes as target so when a weapon destroys it, the other weapon needs to know that the object has been already destroyed to avoid sending messages to the ship. So if I could do something like Alife(ship) that would solve my problem.
     
  20. NCarter

    NCarter

    Joined:
    Sep 3, 2005
    Posts:
    686
    I presume you mean "Alive". I'm not sure how you would want it to work. If it's a function that tests if an object has not been destroyed, then it doesn't really help much:

    Code (csharp):
    1. if(something != null) {...}
    2.  
    3. ...vs...
    4.  
    5. if(Alive(something)) {...}
    On the other hand, if you want it to be an event like Update that gets called whenever your object is 'alive', Update already works like that. Once an object has been destroyed, events are no longer called on it.

    In any case, there's no reason to avoid null tests. They're simple and efficient.
     
  21. Barbur

    Barbur

    Joined:
    Oct 30, 2009
    Posts:
    160
    But sometimes you destroy an object in a piece of code and then later on you try to access to this object through another class instance. The GameObject will still be alive until the next update so a nice way to check if the engine has this object ready to delete would be using a Alive engine function that returns you a boolean saying if the object is going to be deleted or not to prevent using it.
     
    neonblitzer likes this.
  22. NCarter

    NCarter

    Joined:
    Sep 3, 2005
    Posts:
    686
    Well, a null test already does exactly that, except for the brief period between when you call Destroy and the end of the frame. It doesn't really matter that the object hangs around until the end of the frame, because anything that cares about its lifetime will have to check that it's still there during the next frame anyway, and at that point a null test is sufficient.

    EDIT: actually, on reflection, I can see a situation where it's troublesome. If, say, a spaceship is hit by two bullets that should destroy it in the same frame, you don't want to get two explosions where there should only be one. That's more or less what you said, but it's the spawning extra objects part that causes a visible problem. It's easy to program around that, but it's a little inconvenient that you can't check immediately with a null test.
     
  23. CoCoNutti

    CoCoNutti

    Joined:
    Nov 30, 2009
    Posts:
    513
    Hi

    Have been reading through this post with much interest and applying some of your advice to my scripts.
    However, when I put in the debug test for if the object is null, it doesn't appear (hence the object isn't null).

    This is probably because said object has been respawned a few seconds later. In my game I have 3 types of enemies spawning at intervals. What i want is when one hits a triggerbox, it's 'dead'. Others will respawn behind it (prefabs). Is this why I'm not seeing Null?

    Using the above method would I run into memory issues?

    Cheers
     
  24. Zergling103

    Zergling103

    Joined:
    Aug 16, 2011
    Posts:
    392
    This was an interesting read.

    I really wish Unity would forcibly set all references to destroyed objects to null when the destruction occurs. Surely they must have access to the GC, and the GC knows what objects are referenced by what, so from that they could nullify all of those references.

    It'd sure as hell be a lot cleaner than all of this spooky funny business - our studio ran into the same problem.

    So, good to know: Unity doesn't automatically make everything null in the C# end, although it will make it appear that way through its spooky overloaded comparison operator.

    Personally I'd have preferred that they either go one way or the other. Either Destroy() should force all C# references to null, or, the == operator should not be overloaded.

    By the way, to bypass the overloaded operator, use Object.ReferenceEquals().

    In which case, the following code will log "Object.ReferenceEquals(something, null)", but will not log "something == null".

    Code (csharp):
    1. Destroy(something);
    2.  
    3. yield;                    // Wait for Destroy to take effect
    4.  
    5. if(something == null)
    6.     Debug.Log("something == null");
    7.  
    8. if(Object.ReferenceEquals(something, null))
    9.     Debug.Log("Object.ReferenceEquals(something, null)");
    10.  
     
  25. matheus_inmotionvr

    matheus_inmotionvr

    Joined:
    Oct 3, 2018
    Posts:
    63
  26. odaimoko

    odaimoko

    Joined:
    Jun 28, 2020
    Posts:
    9
    matheus_inmotionvr likes this.