Remove Null Key from Dictionary

n the game, I have a dictionary used to record the HashCode of the GameObject, but the GameObject will be automatically destroyed when switching levels, and then an empty Key appears in the Dictionary, how can I check and remove it?
Example:

Dictionary<GameObject,int> levelGameObjectDict = new Dictionary<GameObject,int>();
GameObject obj = new GameObject(“Test”);
levelGameObjectDict.Add(obj,obj.GetHashCode());

SceneManager.UnloadScene(“LevelScene”);

As shown above, Null Key appears in Dictionary

The GameObject is not really null, but Unity implemented some weird == operator override so it returns true when it is compared to null while the GameObject is marked as destroyed, I assume this can cause issue in the internal Dictionary null checks.

Im still a bit sleepy, so there might be a more obvious solution, but my ideas would be:

  • if all of the levelGameObjects are destroyed anyways then you could simply clear the whole dictionary instead of removing them one by one
  • remove them before you change / unload the current scene
  • create a temporary copy, then clear the original dictionary and manually re-add the entries which are not null / destroyed
1 Like

Here are extensions methods that can be used to check for null keys and remove them.

using System.Collections.Generic;
using System.Linq;

public static class DictionaryExtensions
{
    public static bool HasNullKeys<TKey, TValue>(this Dictionary<TKey, TValue> dictionary) where TKey : UnityEngine.Object
    {
        foreach(var key in dictionary.Keys)
        {
            if(key == null)
            {
                return true;
            }
        }
       
        return false;
    }


    public static void RemoveNullKeys<TKey, TValue>(this Dictionary<TKey, TValue> dictionary) where TKey : UnityEngine.Object
    {
        if(!dictionary.HasNullKeys())
        {
            return;
        }

        foreach(var key in dictionary.Keys.ToArray())
        {
            if(key == null)
            {
                dictionary.Remove(key);
            }
        }
    }
}

Usage:

bool hasNullKeys = levelGameObjectDict.HasNullKeys();
levelGameObjectDict.RemoveNullKeys();

If the GameObjects in the dictionary are only destroyed as a result of a scene being unloaded, then the SceneManager.sceneUnloaded callback could be used to clean up destroyed entries from the dictionary.

SceneManager.sceneUnloaded += levelGameObjectDict.RemoveNullKeys;
2 Likes

I did not run your code, but the Dictionary Remove method throws if you try to remove a null key, which makes sense because it can’t get the hash code of a null value, so it shouldn’t work.

You’re right that that is what happens normally - but destroyed UnityEngine.Objects should get past the null guard because the Dictionary class won’t use the overloaded == operator, as it doesn’t have a where TKey : UnityEngine.Object constraint.

1 Like

Ah right, yeah then calling Remove for the “null” keys would be the easiest solution (unless you want to clear the whole Dictionary, then just use Clear)

1 Like

Ok I will try it later

Thanks SisusCo~

1 Like

Simplest solution is to make a new dictionary and copy only the keys that are not null. Heres an example of your code plus the solution using System.Linq.

Dictionary<GameObject,int> levelGameObjectDict = new Dictionary<GameObject,int>();
GameObject obj = new GameObject("Test");
levelGameObjectDict.Add(obj,obj.GetHashCode());

SceneManager.UnloadScene("LevelScene");

levelGameObjectDict  = levelGameObjectDict
    .Where(kvp=> kvp.key)
    .ToDictionary(kvp=>kvp.Key,kvp=>kvp.Value);

that said, you’d have a far easier time if you saved the hashcode itself or the instance Id as the dictionary key and the gameobject as the value

Hi.Thanks. Is there a custom way to calculate the hashcode of an object?

Every type in C# implements GetHashcode(), Classes, structs, enums, string, int… If you can put a type as a key to a dictionary, it has a GetHashcode() implementation. Internally, thats what Dictionary uses for keys anyway.

if you’re asking about how to implement a GetHashcode() function:
When you make a class override a base class and you override an equals method you are also required by C# to also override GetHashcode(), its a virtual function all types can override. You typically need to determine what values in your class describe an instance’s equality. if you want separate instances to be considered equal then they must compute the same hashcode. However the corollary is not true, It’s allowed for two items not be equal yet have the same hashcode

For Reference types (unless overridden in a base class), base.GetHashcode() basically returns the instance ID for that reference. thus by default if ReferneceEquals(A,B) is true, then it usually means base.GetHashcode() value is the same for both A and B.

If however you want two instances to be considered equal while being different references you need to override both .Equals() and .GetHashcode() methods. equals should just compare the values for the 2 instances, and GetHashcode should discard the base.GetHashcode and perform a factored-sum on the hashcode of all the fields important for the class’s equality.

say for example you have a type thats typically deserialized from an external, remote resource so you’ll often get copies of that type and you need to check if a new copy is different from a local cache

public class NetworkLobbyPlayer
{
   public int lobbyId;
   public int lobbyIndex;
   public Guid playerId;


   public override bool Equals(object other)
   {
      if(other == null) return false;
 
      return this.lobbyId.Equals(other.lobbyId)
         && this.lobbyIndex.Equals(other.lobbyIndex)
         && this.playerId.Equals(other.playerId);
   }

   public override int GetHashCode()
   {
      //unchecked protects against integer overflow. if hash gets too large it will wrap
      // inside performing a factored sum to reduce paired type collisions
      // otherwise {lobbyid:5,lobbyIndex:2} would be considered equal to {lobbyid:2,lobbyIndex:5}
      unchecked
      {
        //usually use prime constants to minimize collisions
        int hash = 37;
        hash = (137 * hash) + lobbyId.GetHashCode();
        hash = (137 * hash) + lobbyIndex.GetHashCode();
        hash = (137 * hash) + playerId.GetHashCode();
        // Note: if a field is a reference type that can be null I'd do something like this
        // hash = (137 * hash) + reference?.GetHashCode()??0;
        // or for unity objects
        // hash = (137 * hash) + (go?go.GetHashCode():0);
        return hash;
      }
   }
}

Now I can then use this to check if a player changed in a slot and update the UI accordingly. Even if the cache I’m holding is a different instance than the copy I pull from the server, they can equate to the same thing and will appear as the same item if used as a key for a dictionary, so long as they have the same values

Thanks ~