How to detect when a class is disposed after OnDestroy?

Hello everyone,

I’m working on a system that updates every object that subscribe to it. Those objects are based on a Serializable class and used in a MonoBehaviour (see the following example).

public class MyComponent : MonoBehaviour
{
    public MyClass SerializableObject;
}

[Serializable]
public class MyClass
{
    public float Number;

    public MyClass() => Updater.Subscribe(this);

    ~MyClass() => Updater.Unsubscribe(this);

    public void Update() => Debug.Log("test");
}

public class Updater : MonoBehaviour
{
    private static List<MyClass> _listeners = new List<MyClass>();

    public void OnDestroy() => _listeners.Clear();

    public void Update()
    {
        for(int i = 0; i < _listeners.Count; ++i)
            _listeners[i].Update();
    }

    static internal void Subscribe(MyClass c)
    {
        _listeners.Add(c);
    }

    static internal void Unsubscribe(MyClass c)
    {
        _listeners.Remove(c);
    }
}

My code is not working properly because of the garbage collecting, when my GameObject (where the MyComponent is) is destroyed, MyClass isn’t destroyed nor disposed (if I use IDisposable).

So I’m wondering if it is even possible without enforcing the Dispose() from the MyComponent.OnDestroy() method (that could create more error than solve my problem ahah).

Thank you for reading this

Just use OnEnable / OnDestroy for pub/sub type stuff.

1 Like

That was not what I was asking for. I want that subscription to be automatically sub/unsub on something that is not a MonoBehaviour, but used by a MonoBehaviour. And looking for something (an interface, an attribute, whatever) that I’m missing and could work for my purpose.

If it doesn’t exist at all, I would try another way (manually sub/unsb for sure).

Oop, my bad.

I’ve always avoided this type of construct because I’m led to believe C# finalization isn’t as easy to reason about as it is (for example) in C++.

Google up C# finalize for more …

Even that’s problematic, as IDisposables used within coroutine-like constructs can avoid having their .Dispose() method called:

https://discussions.unity.com/t/577530

1 Like

That’s a no-no in my book. This just won’t work as you expect it to!
The class may still be subscribed in “Updater” even though the instance is in the garbage bin, waiting to be collected.

Just yesterday I discussed a similar anti-pattern with someone else here on the forum. The anti-pattern being that the object itself is responsible for managing its lifetime in some sort of registry, as follows:

  • object is instantiated
  • object instance registers itself with some outside type (ie in ctor)
  • object (somehow) unregisters itself with some outside type when it is “disposed” (ie with Dispose() or in your case the finalizer)

The proper solution to this is always:

  • instantiate the object (ie via factory or manager or registry itself)
  • “register” this instance object as one of the next steps in the method flow but not from within the instantiated object
  • registry uses object’s public/internal events to subscribe to changes in the object (here: OnValueChanged)
  • destroying the object instance is only allowed by the same class that instantiated the object - either apply discipline or some rigorous checks, possibly even debug-only scans for detached/unparented objects

This is much safer to work with, you retain full control of the lifecycle, and it doesn’t create a circular dependency! In the first use case you have one: registry depends on object, but object also depends on registry - that’s a no-no!

Btw I’ve had no issues with IDisposable but I’m also not a heavy coroutine user nor heavily relying on IDisposable but the above pattern. Not sure what it would be called, probably several patterns in use here.