Unsubscribing Listener in C# Object Instance within MonoBehavior

I have a MonoBehavior attached to a GameObject, and that MonoBehavior has an instance of a C# Object which is not a MonoBehavior. That instance subscribes it’s method to a delegate in the form of an Action. Do I need to manually unsubscribe that listener when the GameObject is destroyed? Here’s an example with some abbreviated code:

public class myComponentClass : Monobehavior
{
    CustomObjectType myInstance = new CustomObjectType();

    private void OnDisable()
    {
        // Do i need to unsubscribe from the action in myInstance here?
    }
}

public class CustomObjectType
{
    public CustomObjectType()
    {
        OtherObjectType.myCustomAction += myMethod;
    }
    public myMethod(){}
}

In this example, do I need to unsubscribe from the Action in myInstance, or is that taken care of somehow? I tried implementing the IDisposable interface in the CustomObjectType class, and unsubscribing in the Dispose method, but it didn’t appear that Dispose was being called when the GameObject was destroyed, which makes me worried about a memory leak. I’m thinking this could be because the instance is still being referenced through the Action.

The solution seems to be manually unsubscribing from the Action when the MonoBehavior’s onDisable is called, which is easy in this case, but could be a headache if you’re dealing with an instance of a complex nested datatype with subscriptions several layers deep, so I’m wondering if this is really necessary, or if there is a simpler alternative solution.

If you’re subscribing to a delegate, and the object that’s subscribing exists temporarily, then you will want to unsubscribe from it when the object is destroyed otherwise you’ll hit null reference errors.

If the object that contains the delegate is being destroyed, I don’t believe you do. Though I usually assign null to those delegates in OnDestroy/OnDisable just to be careful.

My concern is that I don’t fully understand how Unity handles garbage collection when it comes to object instances that aren’t MonoBehaviors. As a result, I don’t know then best way to clean up subscriptions in those instances given that they don’t implement OnDisable or OnDestroy.

I imagine it should follow the same rules as normal C# garbage collection. If you have a reference to a regular C# object, and it’s containing monobehaviour is destroyed, the instance of the object should persist so long as you have a reference to it.

That said I would imagine it’d be good practice not to hold pointers to instances that belong to destroyed parents Objects.

You could also test this pretty easily yourself.

So in this case, since the Action still has a reference to the instance through the subscription, the instance won’t be destroyed when the MonoBehavior is destroyed. I wish there was an easy way to unsubscribe automatically within the non-MonoBehavior class, or a design pattern for avoiding this issue altogether.

Try implementing a finalizer in the class that unsubscribes the event:

public class CustomObjectType
{
    public CustomObjectType()
    {
        OtherObjectType.myCustomAction += myMethod;
    }

    ~CustomObjectType()
    {
        OtherObjectType.myCustomAction -= myMethod;
    }

    public myMethod(){}
}

I usually subscribe during OnEnable and unsubscribe during OnDisable
Alternatively in Awake and Destroy. (depends on the needs)

General rules of event should be like this:

When object with event (event owner) is destroyed/disposed, you should “null” the event (this will unsubscribe all listeners)

When object listening to the event is destroyed/disposed it should unsubscribe itself from the event.

You don’t have to do any of this for events that are private and all listeners are inside this object.

1 Like

The issue in this case is that the subscribed object isn’t a MonoBehavior and so it doesn’t have OnEnable or OnDisable. So I’m wondering if this needs to be handled from the top down by something that is a MonoBehavior, or if there is a design pattern that handles this automatically. For instance, could I handle this automatically from the event owner end?

Unfortunately this didn’t work because the finalizer is never called. As long as the event has a reference to these objects, they won’t be garbage collected, so the unsubscribe code is never run. I did find the finalizer helpful for testing potential solutions though.

If the monobehaviour that has the only references to the child object/action/event is destroyed that should be fine. I.e. all references would be destroyed. If there is reason that a dependency can be removed, then I would recommend some event (or other architectural solution) to repair it. In this case, I believe (based on the example above) that the “CustomObjectType” constructor should also subscribe to a destruction event. You will need to add a destruction event to it.

Your code above has some bugs, for instance you are attempting to tether a method to a class, and not an action or event. Since the naming is a little off for exactly what the sample is doing, I won’t recommend adjustments. (I assume its just to demonstrate the code anyway.)

But, it does appear that the POCO appears to be tying to the event from a monobehaviour. You could easily create an action/delegate on your component class that is triggered during the components OnDestroy or other function. The action could be something like OnDestroyListener. This way you can untie old references when cleaning them up. Example

public class VisualPart : MonoBehaviour
{
    public Action OnShake = () => { };
    public Action OnDestroyCalled = () => { };

    private void OnDestroy()
    {
        OnDestroyCalled();
    }
}

public class OtherPart
{
    public OtherPart(VisualPart part)
    {
        part.OnShake += OnShake;
        part.OnDestroyCalled += () => part.OnShake -= OnShake;
    }

    private void OnShake()
    {
        // ...
    }
}

If the concern is the other way around, then the destruction code to unwire it should be triggered by whatever is destroying the “OtherPart”.

1 Like

I think your answer of having an OnDestroyed event is a nice solution to this issue, as it allows you to easily link the MonoBehavior to various objects that are created within it. Let me clarify a couple of things before I get to the solution though.

You say that when a MonoBehavior is destroyed, all of it’s references are destroyed. This is true of the references, but not the objects themselves. The objects themselves remain in memory until they are garbage collected, which only happens if nothing else references them. In this case, the Action still references that object as one of it’s subscribers, so it isn’t garbage collected. I tested this with a finalizer, and the objects were never destroyed, despite the MonoBehavior being destroyed. They were only destroyed after I unsubscribed them from the Action.

It’s possible that there’s a bug in the code, as you say it’s just for demonstration. For example, I left out the code for the Action, which should look something like this:

public class OtherObjectType
{
    public static Action myCustomAction;
}

Getting to your solution, I think it would look like this in terms of the issue I’m seeing:

public class myComponentClass : Monobehavior
{
    CustomObjectType myInstance;
    public Action onDisabled;

    private void OnEnable()
    {
        myInstance = new CustomObjectType(onDisabled);
    }

    private void OnDisable()
    {
        onDisabled?.Invoke();
    }
}

public class CustomObjectType
{
    public CustomObjectType(Action onDisabled)
    {
        onDisabled += unsubscribeAll;
        OtherObjectType.myCustomAction += myMethod;
    }
    public void myMethod(){}
    public void unsubscribeAll()
    {
         onDisabled -= unsubscribeAll;
         OtherObjectType.myCustomAction -= myMethod;
    }
}