Search code examples
c#eventsevent-handlingpublish-subscribe

When my object is no longer referenced why do its events continue to run?


Say I am making a game and have a base Buff class. I may also have a subclass named ThornsBuff which subscribes to the Player class's Damaged event.

The Player may have a list of Buffs and one of them may be the ThornsBuff. For example:

Test Class

Player player = new Player();
player.ActiveBuffs.Add(new ThornsBuff(player));

ThornsBuff Class

public ThornsBuff(Player player)
{
    player.DamageTaken += player_DamageTaken;
}

private void player_DamageTaken(MessagePlayerDamaged m)
{
    m.Assailant.Stats.Health -= (int)(m.DamageAmount * .25);
}

This is all to illustrate an example. If I were to remove the buff from the list, the event is not detached. Even though the player no longer has the buff, the event is still executed as if he did.

Now, I could have a Dispel method to unregister the event, but that forces the developer to call Dispel in addition to removing the Buff from the list. What if they forget, increased coupling, etc.

What I don't understand is why the event doesn't detach itself automatically when the Buff is removed from the list. The Buff only existed in the list and that is its one reference. After it is removed shouldn't the event be detached?

I tried adding the detaching code to the finalizer of the Buff but that didn't fix it either. The event is still running even after it has 0 references. I suppose it is because the garbage collector had not run yet? Is there any way to make it automatic and instant so that when the object has no references all its events are unregistered?


Solution

  • The event (player_DamageTaken) will continue to be called regardless of whether the Buff is in the ActiveBuffs list or not. This is because when you run DamageTaken += player_DamageTaken then it creates a reference from player.DamageTaken to that instance of ThornsBuff and its player_DamageTaken method. (Otherwise, how would the runtime know which instance of ThornsBuff to call player_DamageTaken on?)

    That reference will continue to exist until you call player.DamageTaken -= player_DamageTaken. The fact that you also add the Buff to ActiveBuffs simply adds another reference to the buff, which isn't technically necessary (but it's good form in my opinion since I don't like having non-referenced event handlers hanging around).

    Now you're probably wondering how to remove the event handler without causing the developer to call an extra Dispel or Dispose method, and for that here are some suggestions:

    1. At the beginning of player_DamageTaken do a check to see if player.ActiveBuffs.Contains(this), and if not, call player.DamageTaken -= player_DamageTaken and return. The disadvantage to this is that you're searching the entire list of Buffs every time they take damage.

    2. Make ActiveBuffs an ObservableCollection (or something similar) that can listen for when buffs are removed from the list. When they are, you can call buff.Dispel automatically so that the developer doesn't have to remember to call it.