Quick answer: Always call GodotObject.IsInstanceValid(node) before touching a long-lived reference. Disconnect signals in _ExitTree. Null out cached references when the source node frees.
Two scenes share a manager. Scene A frees a child while Scene B holds a reference to it. Scene B’s next callback throws “ObjectDisposedException: Cannot access a disposed object.” The native object is gone; the C# wrapper noticed.
The Symptom
ObjectDisposedException thrown from C# code that touches a Godot Node. The exception happens at unpredictable times because the underlying free was triggered elsewhere.
What Causes This
Godot manages objects natively. C# wraps them through a marshalled handle. QueueFree on a node destroys the native object at the next idle. Any C# reference that survives is a wrapper around a now-destroyed native object — access throws.
The Fix
Pattern 1: Validity check before use.
if (GodotObject.IsInstanceValid(_target))
{
_target.Position = newPos;
}
else
{
_target = null;
}
Pattern 2: Subscribe to TreeExited.
public override void _Ready()
{
_target = GetNode<Node2D>("Player");
_target.TreeExited += () => _target = null;
}
The TreeExited signal fires before the dispose. Null out your reference immediately so subsequent code never touches the dead wrapper.
Pattern 3: Disconnect in _ExitTree.
public override void _ExitTree()
{
if (GodotObject.IsInstanceValid(_publisher))
_publisher.MySignal -= Handler;
}
Long-lived publishers shouldn’t fire callbacks into disposed listeners.
Async / Await Pitfall
Awaits resume on the SynchronizationContext. The node may be disposed by the time the continuation runs:
public async Task DoWork()
{
await Task.Delay(2000);
if (!GodotObject.IsInstanceValid(this)) return; // guard
Position += Vector2.Right;
}
Or use UniTask’s WithCancellation tied to a TreeExited token (third-party packages bring this to Godot).
Verifying
Reproduce the free-then-touch scenario. Without IsInstanceValid: throws. With: silent return. Add Debug.Print to confirm the guard fires.
“IsInstanceValid before touch. Disconnect in _ExitTree. Null on TreeExited. No more disposed exceptions.”
Related Issues
For C# trim issues, see trim removed. For signal listener missing, see signal listener.
Validate. Null. Disconnect. Wrappers stay safe.