{"record":{"id":"e6d963537e98bee0","repo":"stride3d/stride","slug":"a-dependency-object-must-be-attached-in-order-to-unregister","errorCode":null,"errorMessage":"A dependency object must be attached in order to unregister a handler.","messagePattern":"A dependency object must be attached in order to unregister a handler\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"sources/presentation/Stride.Core.Presentation.Wpf/Core/DependencyPropertyWatcher.cs","lineNumber":120,"sourceCode":"        {\n            if (property == null) throw new ArgumentNullException(nameof(property));\n            if (handler == null) throw new ArgumentNullException(nameof(handler));\n            if (frameworkElement == null) throw new InvalidOperationException(\"A dependency object must be attached in order to register a handler.\");\n\n            DependencyPropertyDescriptor descriptor;\n            if (!descriptors.TryGetValue(property, out descriptor))\n            {\n                descriptor = DependencyPropertyDescriptor.FromProperty(property, AssociatedObject.GetType());\n                descriptors.Add(property, descriptor);\n            }\n            descriptor.AddValueChanged(AssociatedObject, handler);\n        }\n\n        private void DetachHandler([NotNull] DependencyProperty property, [NotNull] EventHandler handler)\n        {\n            if (property == null) throw new ArgumentNullException(nameof(property));\n            if (handler == null) throw new ArgumentNullException(nameof(handler));\n            if (frameworkElement == null) throw new InvalidOperationException(\"A dependency object must be attached in order to unregister a handler.\");\n\n            DependencyPropertyDescriptor descriptor;\n            if (!descriptors.TryGetValue(property, out descriptor))\n            {\n                throw new InvalidOperationException(\"No handler was previously registered for this dependency property.\");\n            }\n            descriptor.RemoveValueChanged(AssociatedObject, handler);\n        }\n\n        private void ElementLoaded(object sender, RoutedEventArgs e)\n        {\n            AttachHandlers();\n        }\n\n        private void ElementUnloaded(object sender, RoutedEventArgs e)\n        {\n            DetachHandlers();\n        }","sourceCodeStart":102,"sourceCodeEnd":138,"githubUrl":"https://github.com/stride3d/stride/blob/96fad776d210c221682aac1ccdf4c79dc046fc38/sources/presentation/Stride.Core.Presentation.Wpf/Core/DependencyPropertyWatcher.cs#L102-L138","documentation":"DetachHandler requires the watcher to still be attached to a FrameworkElement; if frameworkElement is null it throws InvalidOperationException 'A dependency object must be attached in order to unregister a handler.' Removing a value-change subscription needs the same live DependencyObject that AddValueChanged was called with. Called by UnregisterValueChangedHander and DetachHandlers.","triggerScenarios":"Calling UnregisterValueChangedHander after the watcher was detached (element reference cleared, e.g. by Detach() or after the element unloaded) or before it was ever attached.","commonSituations":"Double-detach in unload/unloaded handlers; WPF element unloading (page navigation, ItemsControl recycling) followed by explicit per-handler unregister calls; application shutdown paths racing with cleanup code.","solutions":["Track attach state and skip per-handler unregister calls once the watcher is detached — Detach/DetachHandlers already performs full cleanup.","Perform unregistering inside the element's Unloaded handler before calling watcher Detach, or rely solely on the watcher's own detach path.","Avoid double-detach by guarding with a bool flag or checking watcher attach state.","Re-attach the watcher to the element before attempting targeted unregister if the element is live again."],"exampleFix":"// before\nwatcher.Detach();\nwatcher.UnregisterValueChangedHander(prop, OnChanged); // already detached\n// after\nwatcher.UnregisterValueChangedHander(prop, OnChanged);\nwatcher.Detach();","handlingStrategy":"validation","validationCode":"if (watcher.AssociatedObject == null) { /* already detached; nothing to unregister */ return; }","typeGuard":"static bool CanUnregisterFromWatcher(DependencyPropertyWatcher w) => w?.AssociatedObject is not null;","tryCatchPattern":"try { watcher.UnregisterValueChangedHander(prop, handler); }\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"must be attached\")) { /* watcher already detached; safe to ignore */ }","preventionTips":["Rely on the watcher's Detach() for full cleanup instead of mixed manual unregister + detach paths.","Track detach state with a bool flag to avoid double-detach.","Order teardown: unregister first, then detach.","Treat element Unloaded as the single cleanup entry point."],"tags":["wpf","invalid-operation","lifecycle"],"backgroundTag":"invalid-state-transition","analyzedSha":"96fad776d210c221682aac1ccdf4c79dc046fc38","analyzedAt":"2026-09-14T02:59:31.279Z","contentChangedAt":"2026-09-14T02:59:31.279Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}