Search Unity

  1. Unity 6 Preview is now available. To find out what's new, have a look at our Unity 6 Preview blog post.
    Dismiss Notice
  2. Unity is excited to announce that we will be collaborating with TheXPlace for a summer game jam from June 13 - June 19. Learn more.
    Dismiss Notice

Testing for a Destroyed object... that's an interface.

Discussion in 'Scripting' started by UnbridledGames, Oct 17, 2020.

  1. UnbridledGames

    UnbridledGames

    Joined:
    May 12, 2020
    Posts:
    139
    So I have an interface, IHasContextMenu. I attach that to some scripts that attach to gameobjects, and that provides methods that allow right-click popup menus when those objects are right-clicked.

    Recently, I made some changes to some prefabs where those scripts are added to some gameobjects dynamically. Gameobject is instantiated from a prefab, placed in an object pool, and when it's needed, it's given out. The correct ItemTypeScript is applied (which has the IHasContextMenu interface) and all is well.

    Problem: When that object is released, and the ItemTypeScript is destroyed, the script that handles the context menu, which caches the IHasContextMenu reference to the script stops working right.

    Did some research. Unity objects which are Destroyed will still return false if tested for == null. Ok, got it. Website I found said that instead of testing == null, just using the object itself returns a bool, so you can test something like:

    public SomeScript scriptRef;

    Destroy(scriptRef);
    if(scriptRef == null) {
    // This will NOT run, if the scriptRef has been Destroyed. testing == null will eval false because weird Unity stuff
    }
    if(!scriptRef) {
    // THIS will correctly evaluate, as Unity gameobjects have overloads for this
    }


    The problem I'm facing is I'm not using a reference to a SCRIPT. I'm using a reference to an INTERFACE. The interface resides on quite a few different scripts, so caching the reference to IHasContextMenu and just calling contextMenuRef.menuItems works great.

    Except that once that reference is Destroy'ed, testing == null results in false. And apparently, I can NOT do a boolean if(!contextMenuRef) because that's not a built in overload.

    So if a cached reference to an interface is Destroyed, I can't find a way to test for that. Yes, I COULD make sure the destroying script also sets the reference to null, but that is a LOT of extra code in a lot of places.

    Is there a decent solution for this? The best I could think of was checking if GetComponent<IHasContextMenu>() returned anything, but that seems excessive.
     
  2. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    7,932
    If you really want to rely on Unity's destroyed object magic, you could probably do something like this:

    Code (CSharp):
    1. if (!(scriptRef as UnityEngine.Object)) {
    2.   // If we got here, the object was destroyed (or never assigned, or it's not a UnityEngine.Object).
    3. }
     
    Forberg, atkins79 and Bunny83 like this.
  3. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    39,033
    This is a pickle to be sure. The null check is effectively useless.

    One thing I've done is when I implement the interface, check if
    .gameObject
    has been destroyed or not, and don't do the rest of the function. That doesn't get you through all cases (such as needing to return a valid thing), but it's something.

    An alternate plan is to loosen the coupling, such that the interface functions only transact against local variables (such as a mailbox or queue) and then Update() is charged with doing something with the posted data. Again, doesn't handle all cases, such as "give me your X" case.

    If you must, you could always implement a second interface,
    IExistable
    and have all calling sites responsible for getting that and testing it. Alternately you could implement a
    bool DoYouStillExist()
    method into ALL your interfaces.

    In all cases, meh, not great. But interfaces are still greatly worthwhile, imnsho.
     
  4. UnbridledGames

    UnbridledGames

    Joined:
    May 12, 2020
    Posts:
    139
    What I did for now, due to the lack of a good/elegant solution, is check for the GetComponent<IMyInterface> still exists on the gameObject in question. These checks don't happen that often, so it's not like it will cause any performance issues. It just feels sloppy and I like my code to be a bit cleaner than that.

    I've worked a little with operator overloads - I had a lot of fun implementing a custom == overload for my ItemData so I could test a few different things. Totally not necessary, a method would have handled it just fine, but, was a cool learning experience.

    That being said, how would you overload something to be testable as bool with a simple if check, since technically it's not using any operator.

    If that question isn't clear, it's based off of my origial question above.

    SomeScript scriptInstance;

    scriptInstance = AddComponent<SomeScript>();

    Destroy(scriptInstance);

    if(scriptInstance) {
    // I'm still here!
    } else {
    // Nope, I was destroyed
    }


    That if check... personally I love those checks. I come from a long background in C and it took me a long while to get used to all the == null and != null checks, when I used to just do if(!thing).

    How would you go about overloading that?
     
  5. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    7,932
    Actually it is using an operator!

    You would create an implicit conversion-to-bool operator. Here's some documentation from Microsoft on how to write conversion operators in C#: https://docs.microsoft.com/en-us/do...e/operators/user-defined-conversion-operators

    Their example in that link is for conversion between Digit and byte, but you can write your own between any two types you want.
     
    UnbridledGames likes this.
  6. UnbridledGames

    UnbridledGames

    Joined:
    May 12, 2020
    Posts:
    139
    Thanks for the link. One of the hardest parts (for me anyway) of learning new coding techniques/languages is not knowing the terminology to even google for. I'll give that a read. Thanks again.
     
  7. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    4,113
    Unity actually implements an implicit type conversion operator from UnityEngine.Object to bool. That's why you can simply use a reference of type UnityEngine.Object in place where a boolean is expected (like in an if statement). Note that type conversion operators can only be implemented in one of the two types you want to convert between. So this is not really an option for interfaces since you can not declare operators inside interfaces. Since we want a conversion to bool that's also not an option. The best solution for interfaces would be an extension method. Extension methods have to be declared inside a static class. So you can use a method like this:

    Code (CSharp):
    1. public static class UnityObjectAliveExtension
    2. {
    3.     public static bool IsAlive(this object aObj)
    4.     {
    5.         var o = aObj as UnityEngine.Object;
    6.         return o != null
    7.     }
    8. }
    9.  
    This would essentially give every type an IsAlive method which would always return false except it it's a UnityEngine.Object derived type and it's still alive. Note that I would not recommend attaching an extension method to the System.Object type as this method would be valid on literally any type. Though it would be useless on all types except UnityEngine.Object derived types. For example this would be possible as well:

    Code (CSharp):
    1. int v = 5;
    2. if (v.IsAlive())
    3.     // will always be false since the as-cast inside the method will fail and evaluate to null
    4.  
    So I would recommend to create a seperate interface like IHasAliveCheck and restrict the extension method to this interface:

    Code (CSharp):
    1.  
    2. public interface IHasAliveCheck { }
    3. public static class UnityObjectAliveExtension
    4. {
    5.     public static bool IsAlive(this IHasAliveCheck aObj)
    6.     {
    7.         if (aObj is UnityEngine.Object o)
    8.             return o != null;
    9.         return aObj != null;
    10.     }
    11. }
    12.  
    13.  
    Now your own interface can simply implement this IHasAliveCheck interface and it gets the extention method:

    Code (CSharp):
    1. public interface IHasContextMenu : IHasAliveCheck
    2. {
    3.     // [ ... ]
    4. }
    That's still kinda hacky but it provides a universal null check that does handle UnityEngine.Object objects correctly. So only when the passed object is of type UnityEngine.Object we use Unity's custom null check. In all other cases we just use the ordinary null check. This IsAlive method will always return true except when the passed reference is either null or a fake null UnityEngine.Object.

    Note that extension methods are just syntactic sugar in the compiler. It's essentially a fallback solution. However that means if your actual interface has its own IsAlive method, the extension method would not work / would not be visible. They only kick in when the compiler can't find a method with this name / signature.

    Technically instance methods of objects work just like extension methods. The actual code for all methods is always static. Instance methods just have an implicit "first" argument which is the instance reference they should be executed on. All this is just hidded behind syntactic sugar of the compiler. The only methods that work a bit differently are virtual / abstract method or interface methods as they require dynamic dispatch. Though after the resolving of the actual method it again works the same way. This can be seen when looking at the CreateDelegate method of the reflection system. For instance methods you have to provide the "firstArgument" which essentially provides the "this" reference inside the method itself. See this blog on dynamic dispatch in C#.

    The programming language Lua actually has a neat feature that provides some sort of "instance" methods for tables. Lua has the
    .
    (dot) operator to access a value inside a table by name. Since functions in lua can simply be stored in a table value you can call methods like this

    someTable.myFunc()


    In addition to the "dot" operator lua provides the "colon" operator
    :
    . This is also just syntactic sugar. It simply passes the table as the first argument. So when doing

    someTable:myFunc(5, 3)


    you actually do

    someTable.myFunc(someTable, 5, 3)
     
    _geo__, troyspencer and BigRookGames like this.
  8. unity_ctg0ujCJYRi52g

    unity_ctg0ujCJYRi52g

    Joined:
    Jun 1, 2020
    Posts:
    11
    I don't know if its the best solution but for me worked smth like this:
    Code (CSharp):
    1. if(scriptRef == null)
    Code (CSharp):
    1. if((Object)scriptRef == null)
     
  9. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    4,113
    This only works if you can guarantee that the object behind the interface is an UnityEngine.Object derived class. You would get an invalid cast exception in any other case. You could use an "as" cast instead. Though this would just silently filter out any valid instances which are not derived from MonoBehaviour or ScriptableObject. Depending on your usecase it may not matter. Though it does not apply to all cases.
     
  10. Allekq07

    Allekq07

    Joined:
    Jan 21, 2021
    Posts:
    2
    i just use something like this:

    if (interface == null || interface.Equals(null))
    {
    //doesnt exist
    }
    else
    {
    //exists
    }


    no idea why this works, but it does
     
    SisusCo likes this.
  11. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    4,113
    Yes, this is a way because operators like "==" are not virtual and can not be overridded while the Equals method is a virtual method and the UnityEngine.Object class has overridded it properly.

    The issue is that the compiler chooses which operator it should use based on the variable type, not on the actual type which is unknown at compile time. Virtual methods are resolved at runtime as classes which have virtual methods have a v-table (virtual function pointer table) and when you call a virtual method, the call actually looks up which actual method should be used. Though as I said, operators are not virtual methods. So when you store a UnityEngine.Object type in an interface type variable (or System.Object type variable), the custom == operator that Unity provides is not used but the default one.

    Your solution has two steps:
    1. The normal == operator is used which can only detect true null values. So when the null check tells you that there actually is an instance, that instance may be "dead" / "fake null".
    2. You can only call the Equals method on an actual instance, so the first null check is necessary. The virtual Equals method of UnityEngine.Object types will return true when you compare against "null" if the object is dead. So it fakes that it is null.
     
    SisusCo and Ryiah like this.
  12. Nad_B

    Nad_B

    Joined:
    Aug 1, 2021
    Posts:
    730
    The thing is, when you cast a Unity object to an interface (either directly or by using GetComponent<IInterface>) C# won't use any operator override/implicit/explicit converters from the Unity object, which bypasses the Unity objects "fake" null check (i.e. Unity considers an object as null if it has been destroyed, even if the C# object is still alive).

    What I use in my interfaces to solve this is just to add an "IsAlive" bool to my interfaces. Then in Unity objects I implement it as:
    Code (CSharp):
    1. public interface IHasContextMenu
    2. {
    3.     bool IsAlive { get; }
    4.     // Other IHasContextMenu properties/methods...
    5. }
    6.  
    7. public class MyClass : MonoBehaviour, IHasContextMenu
    8. {
    9.     public bool IsAlive => gameObject != null;
    10.     // Other IHasContextMenu properties/methods...
    11. }
    If you have a lot of interfaces that need to have an IsAlive check, just create a base interface (for eg. IGameEntity) that has the bool IsAlive (and any other useful shared properties like Transform...etc) and make other interfaces inherit from it.

    You could also go the extension method way like @Bunny83 suggested. But personally I like to be explicit in my code, so I prefer a bool in the interface itself.
     
    Last edited: Apr 13, 2024
    Bunny83 likes this.
  13. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    4,113
    Right. Hopefully in the future we get something like that natively. They were thinking about deprecating the == operator overload but I think they considered it a too big of a change as it would even break a lot if internal code. So they would need to introduce a new system in parallel, switch all internal stuff and much later may get rid of the operator.

    Though many were begging to get rid of the operator but they don't quite get the implications. All the fancy null operators would still not work. So things like
    ??
    ,
    is not null
    or
    ?.
    still wouldn't do what is intended.

    Extention methods have the advantage that they also work with true null references. Your IsAlive bool check always needs to be paired with a null check. While technically this is always necessary, an extension method can do both things at once. Though requiring both check maybe would force the users to learn and understand the reason behind it and why it's actually necessary :)
     
    Nad_B likes this.
  14. SisusCo

    SisusCo

    Joined:
    Jan 29, 2019
    Posts:
    1,345
    I haven't realized this before now, but the fact that
    UnityEngine.Object.Equals(null)
    can't handle actual null references, only destroyed objects, can be a good thing as well.

    For example, consider this code:
    Code (CSharp):
    1. class OnDisableExecuteCommand : MonoBehaviour
    2. {
    3.     ICommand command;
    4.  
    5.     void Awake() => command = GetComponent<ICommand>();
    6.  
    7.     OnDisable()
    8.     {
    9.         if(!command.Equals(null))
    10.         {
    11.             command.Execute();
    12.         }
    13.     }
    14. }
    Here I would see it as very beneficial that the null check can't accidentally hide erroneous situations where no ICommand component was ever attached to the game object in the first place.


    But the way I usually handle null-checking interface type variables is like this:
    Code (CSharp):
    1. void OnDisable()
    2. {
    3.     if(command != Null)
    4.     {
    5.         command.Execute();
    6.     }
    7. }
    Where the
    Null
    object's class has an overloaded
    ==
    operator, which can handle destroyed objects as well:
    Code (CSharp):
    1. public static bool operator ==(object @object, Null @null)
    2.     => @object is Object unityObject ? unityObject == null : @object is null;
    It always bothers me a little bit when the argument with the
    this
    modifier in an extension method is null-checked; I worry that it's not very intuitive to somebody using the extension method, that it can in fact handle null references.
     
    Nad_B likes this.
  15. Nad_B

    Nad_B

    Joined:
    Aug 1, 2021
    Posts:
    730
    Yes that's why nullable references is such a great feature in recent C# versions, as it convey if the method accepts a null object or not. There's also [CanBeNull]/[NotNull] attributes if you're using Rider or VS with Resharper with older C# versions that do not support nullable references.
     
    SisusCo likes this.
  16. SisusCo

    SisusCo

    Joined:
    Jan 29, 2019
    Posts:
    1,345
    @Nad_B Good point! I haven't enabled nullable reference types in any of my projects yet, but I'm planning to do so in the next one I start. They'll probably make it much more difficult to do accidental error hiding in situations like this.

    I've switched over from JetBrain's CanBeNull/NotNull to Microsoft's variants AllowNull/DisallowNull/MaybeNull/NotNull - and the rest of them. These ones are supported on VS without ReSharper as well.

    I like being very explicit about which parameter and return values are allowed to be null, and getting static analysis to help ensure that these contracts are followed is totally awesome :cool:
     
    Nad_B likes this.