Hi folks,
I have a custom control element and this element listens to an Action. I create the element and then delete it seamlessly but when I delete the element it still has a listener handler and it doesn’t give an error!
public class GridCellItem : VisualElement, IDisposable
{
public GridCellItem()
{
AddToClassList("grid-cell");
TestEvents.OnTestAction += OnTestAction;
RegisterCallback<DetachFromPanelEvent>(OnDetachFromPanelEvent);
}
private void OnTestAction()
{
Debug.Log($"Test:{enabledSelf}/{enabledInHierarchy}");
}
public void Dispose()
{
RemoveFromHierarchy();
}
}
I delete the item with “RemoveFromHierarchy()” method and it is deleted from Hierarchy. I also test on UIToolkit-Debugger.
But when I trigger “TestEvents.OnTestAction” event, the deleted element’s OnTestAction() method is triggered and It doesn’t give any error too.
Output:
Tes: True/True
I can remove the listener in “Dispose” method but, I’m really confused that how deleted element still accessible?
This is the nature of c#. TestEvents.OnTestAction is holding a reference to your element, this keeps the element alive and prevents garbage collection.
You should remove the reference, either with the IDispose or try using the DetachFronPanel event. It’s good practice to unsubscribe from events, otherwise you end up with situations like this or events full of empty delegates.
Thanks for your so fast reply.
Yes, absolutely you are right. I confused because of “gameObject” instance. When I was working with MonoBehaviour instance I always got error if I try to access the its GameObject or its properties.
I would like to clarify - is the DetachFronPanel event a recommended place to unsubscribe from events? I know that it seems like all events registered using the RegisterCallback syntax are deleted on their own, and unsubscription is not required (please correct me if I’m wrong). However, there are still places in the UI Toolkit where we use C# events, such as button.clicked. Where and how should I unsubscribe from events in order to avoid blocking the garbage collector for UI Element?
As always it depends. At the end of the day, underneath a lot of abstraction, RegisterCallback<TEvent> still ultimately subscribes a method to a C# delegate. Delegates hold onto a reference to the object that owns the subscribed method, so as long as the object containing said delegate is alive in memory, so will the owner object of the subscribed method.
So if a visual element registers its own method via RegisterCallback, for example, that’s fine. It only has a reference itself, but can still end up ‘unreachable’ during GC and will be cleaned up.
Though if you have transient visual elements registering callbacks to a persistent visual element, and they don’t unregister, then they will remain in memory until the persistent element is cleaned up.
Wherever it works. There’s no one right way. I do a lot of event subscription in AttachToPanelEvent and then clean it up in DetatchFromPanelEvent.
Though you may, for example, have a monobehaviour that wants to register callbacks, so you would then want to subscribe in OnEnable and unsubscribe in OnDisable.