{"record":{"id":"2dba06a8a2371959","repo":"stride3d/stride","slug":"propertychanged-is-invoked-without-propertychanging-or","errorCode":null,"errorMessage":"PropertyChanged is invoked without PropertyChanging, or multiple items in the collection are changing concurrently.","messagePattern":"PropertyChanged is invoked without PropertyChanging, or multiple items in the collection are changing concurrently\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"sources/presentation/Stride.Core.Presentation/Collections/AutoUpdatingSortedObservableCollection.cs","lineNumber":132,"sourceCode":"            throw new InvalidOperationException(\"Multiple items in the collection are changing concurrently.\");\n\n        ++changeCount;\n\n        ChangingItem = item;\n        ChangingIndex = GetIndex(item, false);\n        AddedItem = null;\n    }\n\n    protected virtual void ItemPropertyChanged(object? sender, PropertyChangedEventArgs e)\n    {\n        if (e.PropertyName is null || propertyNames?.Contains(e.PropertyName) == false)\n            return;\n\n        var item = (T?)sender;\n\n        // An object has been added while a property of an existing object has been modified\n        if (ChangingItem is not null && AddedItem is not null)\n            throw new InvalidOperationException(\"PropertyChanged is invoked without PropertyChanging, or multiple items in the collection are changing concurrently.\");\n\n        // The object has been added to the collection during the property change, so it was not preregistered by the ItemPropertyChanging event\n        if (ChangingItem is null && AddedItem is not null)\n        {\n            ChangingItem = AddedItem;\n            ChangingIndex = AddedIndex;\n            ++changeCount;\n        }\n\n        // No object is changing, or a different object is currently changing\n        if (ChangingItem is null || !ReferenceEquals(ChangingItem, item))\n        {\n            throw new InvalidOperationException(\"PropertyChanged is invoked without PropertyChanging, or multiple items in the collection are changing concurrently.\");\n        }\n\n        --changeCount;\n        if (changeCount == 0)\n        {","sourceCodeStart":114,"sourceCodeEnd":150,"githubUrl":"https://github.com/stride3d/stride/blob/96fad776d210c221682aac1ccdf4c79dc046fc38/sources/presentation/Stride.Core.Presentation/Collections/AutoUpdatingSortedObservableCollection.cs#L114-L150","documentation":"ItemPropertyChanged validates that a PropertyChanged notification is matched by a prior PropertyChanging for the same item. If both ChangingItem and AddedItem are set (a new item was added while another item reports a property change) the tracking state is inconsistent, so it throws. This can also fire when PropertyChanged arrives with no PropertyChanging at all.","triggerScenarios":"An item raising PropertyChanged without ever raising PropertyChanging; an item added to the collection inside another item's property-change scope; mis-ordered raise calls in a SetProperty helper.","commonSituations":"Hand-written setters that only raise PropertyChanged; adding items inside a property setter; third-party view-models with incomplete INotifyPropertyChanged implementations used as collection items.","solutions":["Raise PropertyChanging before every PropertyChanged for the same property on the same object (use SetProperty/PropertyChangedBase helpers)","Do not add items to the collection while another item's property change is in flight","Fix the item class's INotifyPropertyChanged ordering (Changing first, then Changed)","Verify all item types derive from a base implementing correct change-notification pairs"],"exampleFix":"// before\npublic string Name\n{\n    get => name;\n    set { name = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name))); }\n}\n// after\npublic string Name\n{\n    get => name;\n    set\n    {\n        PropertyChanging?.Invoke(this, new PropertyChangingEventArgs(nameof(Name)));\n        name = value;\n        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name)));\n    }\n}","handlingStrategy":"validation","validationCode":"// Verify item types raise Changing before Changed: raiseChanging(name); value = x; raiseChanged(name);","typeGuard":null,"tryCatchPattern":"try { collection.Add(item); } catch (InvalidOperationException ex) { logger.Error(ex, \"unmatched PropertyChanged\"); }","preventionTips":["Derive items from a base implementing correct Changing/Changed pairing (e.g. PropertyChangedBase)","Never raise PropertyChanged without PropertyChanging for the same property","Do not Add items to the collection inside another item's property-change scope"],"tags":["collections","inotifypropertychanged","observable"],"backgroundTag":"internal-invariant-violation","analyzedSha":"96fad776d210c221682aac1ccdf4c79dc046fc38","analyzedAt":"2026-09-14T02:59:31.279Z","contentChangedAt":"2026-09-14T02:59:31.279Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}