Repository: godot
Issue: IsConnected does not work as expected in C# with editor-connected signals, custom signals, or signals with parameters
Labels: bug,topic:dotnet

### Godot version

4.0.2

### System information

macOS 13.3.1 (Ventura)

### Issue description

Although IsConnected works as expected when a built-in parameterless signal has been connected using the `+= MethodName` syntax, it does not work as expected for

- any signal connected through the Godot Editor
- any signal with parameters
- any custom signal created using the `[Signal]` decorator, whether connected through a script or in the Godot Editor and whether it has parameters or not, and connected using the `+= MethodName` syntax

A common use for IsConnected is to avoid unintended multiple-subscriptions; the C#-idiomatic way of doing this by using `-=` before `+=` used to produce a Godot error (described in #72994) but should be fixed by #73730. However, there might be other situations where a developer relies on IsConnected, so looking at a fix seems prudent.

(C#, to my knowledge, does not provide its own way to check whether a given event handler is already subscribed to an event. This is apparently [by design](https://stackoverflow.com/questions/2697247/how-to-determine-if-an-event-is-already-subscribed).)

Built-in parameterless signals added using `+=` work as expected:
```
emitter.Renamed += OnEmitterRenamedConnectedInScript;
emitter.IsConnected(Node.SignalName.Renamed, Callable.From(OnEmitterRenamedConnectedInScript)) // returns True, as expected
```

But those added in the editor do not:
```
emitter.IsConnected(Node.SignalName.Renamed, Callable.From(OnEmitterRenamedConnectedInEditor)) // returns False
```

Custom parameterless signals do not work as expected, whether added in the editor or using `+=`:
```
emitter.ZeroParameter += OnZeroParameterConnectInScript;
emitter.IsConnected(SignalEmitter.SignalName.ZeroParameter, Callable.From(OnZeroParameterConnectInScript)); // returns False
emitter.IsConnected(SignalEmitter.SignalName.ZeroParameter, Callable.From(OnZeroParameterConnectInEditor)); // returns False
```

Signals with parameters must first be put into Action<> delegate objects before they are turned into Callables for use with IsConnected because something like `emitter.IsConnected(SignalEmitter.SignalName.MultipleParameter, Callable.From(OnMultipleParameterConnectInScript))` throws CS1503 "cannot convert from 'method group' to 'Action'", but passing such Callables to IsConnected also does not work as expected for any signal with parameters, whether built-in or custom, and whether added using `+=` or in the editor:
```
emitter.MultipleParameter += OnMultipleParameter;
Action<int, int> onMultipleParameter = OnMultipleParameter;
emitter.IsConnected(SignalEmitter.SignalName.MultipleParameter, Callable.From(onMultipleParameter)); // returns False
```

However, there is one situation where IsConnected works with multi-parameter signals, albeit a non-obvious one that the documentation does not mention and a developer would be unlikely to choose. Action<> delegate objects cannot be used directly with `+=` syntax (throws CS0029) but they _can_ be used with the Connect method via Callable.From; signals connected in this way _can_ be checked with IsConnected. This works even from other scopes:
```
Action<int, int> onMultipleParameter = OnMultipleParameter;
emitter.Connect(SignalEmitter.SignalName.MultipleParameter, Callable.From(onMultipleParameter));
emitter.IsConnected(SignalEmitter.SignalName.MultipleParameter, Callable.From(onMultipleParameter)); // returns True
DemonstrateOtherScope(emitter);

...

public void DemonstrateOtherScope(SignalEmitter emitter)
{
    Action<int, int> differentScopeAction = OnMultipleParameter;
    emitter.IsConnected(SignalEmitter.SignalName.MultipleParameter, Callable.From(differentScopeAction)); // returns True
}
```

Though this is a workaround, it's not much of one.

### Steps to reproduce

1. Open the provided MRP with the Mono/.NET 6 version of Godot 4. The node named "IsConnectedMrp" runs IsConnectedMrp.cs, which contains event handlers and demonstrates the issue described by writing to console. The child node named "SignalEmitter" runs SignalEmitter.cs, which defines a simple Node with two custom signals and a method that emits a parameterless built-in signal, a built-in signal with parameters, a parameterless custom signal, and a custom signal with two parameters for demonstration purposes.
2. Run scene IsConnectedMrp.tscn and observe that, although all signals are connected, many of the calls to IsConnected return False:

```
Invoked: OnEmitterRenamedConnectedInEditor
Invoked: OnEmitterRenamedConnectedInScript
Invoked: OnEmitterChildExitingTreeConnectedInEditor, node: <Node#26742883462>
Invoked: OnEmitterChildExitingTreeConnectedInScript, node: <Node#26742883462>
Invoked: OnZeroParameterConnectInEditor
Invoked: OnZeroParameterConnectInScript
Invoked: MultipleParameterConnectInEditor, first: 1, second: 2
Invoked: OnMultipleParameterConnectInScript, first: 1, second: 2
IsConnected with built-in parameterless signals connected in the script:
-> OnEmitterRenamedConnectedInScript IsConnected: True
IsConnected with built-in parameterless signals connected in the editor:
-> OnEmitterRenamedConnectedInEditor IsConnected: False
IsConnected with custom parameterless signals:
-> OnZeroParameterConnectInScript IsConnected: False
-> OnZeroParameterConnectInEditor IsConnected: False
IsConnected using Action delegates to check signals connected in editor and in script using += syntax:
-> OnEmitterChildExitingTreeConnectedInScript IsConnected: False
-> OnEmitterChildExitingTreeConnectedInEditor IsConnected: False
-> OnMultipleParameterConnectInScript IsConnected: False
-> MultipleParameterConnectInEditor IsConnected: False
Invoked: OnEmitterRenamedConnectedInEditor
Invoked: OnEmitterRenamedConnectedInScript
Invoked: OnEmitterChildExitingTreeConnectedInEditor, node: <Node#26742883462>
Invoked: OnEmitterChildExitingTreeConnectedInScript, node: <Node#26742883462>
Invoked: OnEmitterChildExitingTreeConnectedInScript, node: <Node#26742883462>
Invoked: OnZeroParameterConnectInEditor
Invoked: OnZeroParameterConnectInScript
Invoked: MultipleParameterConnectInEditor, first: 1, second: 2
Invoked: OnMultipleParameterConnectInScript, first: 1, second: 2
Invoked: OnMultipleParameterConnectInScript, first: 1, second: 2
IsConnected using Action delegates to check signals connected in script in same scope using Connect with Action delegate:
-> childExitingTreeConnectedInScript IsConnected: True
-> onMultipleParameterConnectInScript IsConnected: True
IsConnected using Action delegates to check signals connected in script in different scope using Connect with Action delegate:
-> childExitingTreeDifferentScope IsConnected: True
-> onMultipleParameterDifferentScope IsConnected: True
```

### Minimal reproduction project

[IsConnectedMrp.zip](https://github.com/godotengine/godot/files/11379332/IsConnectedMrp.zip)