Most efficient way to remove a null object from a dictionary

Each Enemy has a dictionary of buildings and their distances from the enemy. However, once a building gets destroyed it stays in the dictionary as ‘null’. Given that the dictionary is not ordered, what is the most efficient way to remove this object? At the moment I just iterate through with this code:

void Remove()
        {
            //TODO Less resource-hungry
            for(int i = 0; i < distances.Count; i++)
            {
                var key = distances.ElementAt(i).Key;

                if (key == null)
                {
                    distances.Remove(key);
                }
            }
        }

Note that buildings could get destroyed by other enemies too, so ‘removing on destruction’ would be a problem - of course, if anyone can give me a good way to do this it would be awesome. Additionally, you cannot remove ‘null’ keys from a dictionary and the class responsible for destroying the building is not the enemy class, which is only responsible for damaging it, so removing on destruction could be problematic.

Thanks!

Edit: After a lengthy conversation in the comments, I have opted for an event-based system because it also helps with other factors. Thanks everyone for all your help!

Of course using a direct event to remove an element from the dictionary is the most efficient solution as long as you have a back-reference. However if those changes are difficult to implement in your current code, your current solution does work as well but they way you do it is horrible inefficient. The major issue is the usage of the Linq function “ElementAt” inside a loop. Each time you call it you will actually create a new IEnumerable object which will iterate through all elements until index “i” is reached. That means you create “i”-times such an IEnumerable and IEnumerator and they are successive iterated more and more. The complexity is O(n²/2) or just O(n²) since only the highest power is relevant. The biggest issue is the creation of the IEnumerable / IEnumerator instances which need to be garbage collected afterwards.

It’s much more effective to do

List<YourKeyType> toBeRemoved = new List<YourKeyType>();
void Remove()
{
    toBeRemoved.Clear();
    foreach(var kv in distances)
    {
        if (kv.Key == null)
        {
            toBeRemoved.Add(kv.Key);
        }
    }
    for(int i = 0; i < toBeRemoved.Count; i++)
    {
        distances.Remove(toBeRemoved*);*

}
toBeRemoved.Clear();
}
Depending on how you actually use the dictionary you may call this clean up method only every now and then. Since the Dictionary for each enumerator is a struct enumerator, no garbage should be allocated. Since we reuse the toBeRemoved list no additional garbage should be allocated once the list has grown to the required size.
Note that the enumerable of the dictionary iterates over KeyValuePair<TKey, TValue> structs.
We just don’t know enough about your requirements, how and when you use the dictionary for what exact purpose. Since you call is “distances” I would assume it’s something like Dictionary<GameObject, float>? Why exactuy do you use a dictionary here? Is it possible that you try to add the same object multiple times? If you just need the data to find the closest object a simple List with KeyValuePairs would be enough. When you actually looking for the closest object, just iterate the list backwards with a normal for loop and check for a null value. If it’s null you can safely remove it on the fly

You remove the building from the dictionary the moment the building is destroyed.

So you could rewrite it to be something like this.

#Watch Video DEMO

##Building

public enum BuildingState {
	Alive,
	Destroyed
}

public class Building : MonoBehaviour 
{
	public List<Enemy> Attackers = new List<Enemy>();

	private float _health = 1000;
	public float Health 
	{
		get
		{
			return _health;
		}
		set 
		{
			_health = value;
			State = _health > 0 ? BuildingState.Alive : BuildingState.Destroyed;
		}
	}

	public BuildingState State = BuildingState.Alive;

	public void Attack(Enemy sender, float damage) 
	{
		if(!Attackers.Contains(sender))
		{
			Attackers.Add(sender);
		}

		Health -= damage;

		if(State == BuildingState.Destroyed)
		{
			gameObject.SetActive(false);
			Disengage(sender);
		}

		Debug.Log($"{gameObject.name}: {Health}");
	}

	public void Disengage(Enemy sender)
	{
		Attackers.Remove(sender);
	}
}

##Enemy

public class Enemy : MonoBehaviour
{
	public float Radius = 15;
	public Building Target;
	
	void Update()
	{
		if(Input.GetKeyDown(KeyCode.Space))
		{
			Attack(100);
		}
	}

	public void Attack(float damage)
	{
		if(Target != null)
		{
			if(Target.State != BuildingState.Destroyed)
			{
				Target.Attack(this, damage);
			}
			else
			{
				Target.Disengage(this);
				Target = GetClosestBuilding();
			}
		}
		else
		{
			Target = GetClosestBuilding();
			if(Target != null) Attack(damage);
		}
	}

	public Building GetClosestBuilding()
	{
		return GetBuildingsInRange().FirstOrDefault();
	}

	public IEnumerable<Building> GetBuildingsInRange()
	{
		var buildings = new List<Building>();
		var hitColliders = Physics.OverlapSphere(transform.position, Radius);
		
		return hitColliders
			.Where(x => x.tag == "Building")
			.Select(collider => collider.gameObject.GetComponent<Building>())
			.OrderBy(o => Vector3.Distance(o.transform.position, transform.position));
	}
}