How to free nodes with custom C# signals?

Godot Version

4.2

Question

I’m follow the tutorial here which is about having multiple dialog boxes on screen that you can drag around and bring to the front. It’s working fine, my DraggableDialog (PanelContainer) defines a signal:

    [Signal]
    public delegate void MoveDialogToTopEventHandler(DraggableDialog dialog);

Which is used by my Node that holds the various Panels

public partial class DialogBoxHolder : Control
{
	public override void _Ready()
	{
		var children = GetChildren();
		foreach (var child in children)
		{
			if (child is DraggableDialog)
			{
				((DraggableDialog)child).MoveDialogToTop += HandleMoveDialogToTop;

            }
		}
	}

    private void HandleMoveDialogToTop(DraggableDialog dialog)
    {
		MoveChild(dialog, GetChildCount() - 1);
    }
}

The part I’m not sure about, is how to handle when these dialogs are closed. Inside the DraggableDialog I call QueueFree() to remove the dialog from the scene on close. The Godot docs here say that “While all engine signals connected as events are automatically disconnected when nodes are freed, custom signals aren’t. Meaning that: you will need to manually disconnect (using -=) all the custom signals you connected as C# events (using +=).”

So my question is, how do I disconnect from the custom events in the DialogBoxHolder when the underlying Nodes call QueueFree()?

godot signals i have not tried but it you should be able to -= signals from children. here is doing the same thing using Actions.

using Godot;
using System;
public partial class Control:Godot.Control {
	void HelloWorld() {
		GD.Print("Hello, world!");
	}
	void RemoveHelloWorld() {
		foreach(var j_node in GetChildren()) {
			if(j_node is Button _a_button) {
				GD.Print($"removed from {_a_button.Name}");
				_a_button.Pressed -= HelloWorld;
				if("RemoveHelloWorld" == _a_button.Name) _a_button.Pressed -= RemoveHelloWorld;
			}
		}
	}
	public override void _Ready() {
		foreach(var j_node in GetChildren()) {
			if(j_node is Button _a_button) {
				_a_button.Pressed += HelloWorld;
				if("RemoveHelloWorld" == _a_button.Name) _a_button.Pressed += RemoveHelloWorld;
			}
		}
	}
}

if case you were unaware., you don’t have to cast DraggableDialog twice.

if (child is DraggableDialog) {
	((DraggableDialog)child).MoveDialogToTop += HandleMoveDialogToTop;

instead use something like:

if (child is DraggableDialog my_draggable_dialog_variable) {
	// code
}

My interpretation of your response is that if a Node declares a C# custom signal and has code that calls QueueFree(), then it also needs to provide a second deletion signal that notifies subscribers that it is deleting itself and any subscriber to the first signal must also subscribe to the deletion signal so that it knows to disconnect itself from the deleted node.

It feels a little messy to me, but I guess if that’s how custom C# signals work in Godot, it is what it is.

Also, I didn’t know about that “if (a is T t)” syntax, thanks.