{"record":{"id":"ee8c7140c8a01127","repo":"dotnet/wpf","slug":"throw-new-invalidoperationexception-sr-removeditemnotfound","errorCode":null,"errorMessage":"throw new InvalidOperationException(SR.RemovedItemNotFound);","messagePattern":"throw new InvalidOperationException\\(SR\\.RemovedItemNotFound\\);","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/Data/EnumerableCollectionView.cs","lineNumber":420,"sourceCode":"                    if (args.NewStartingIndex < 0 || _snapshot.Count <= args.NewStartingIndex)\n                    {   // append\n                        for (int i = 0; i < args.NewItems.Count; ++i)\n                        {\n                            _snapshot.Add(args.NewItems[i]);\n                        }\n                    }\n                    else\n                    {   // insert\n                        for (int i = args.NewItems.Count - 1; i >= 0; --i)\n                        {\n                            _snapshot.Insert(args.NewStartingIndex, args.NewItems[i]);\n                        }\n                    }\n                    break;\n\n                case NotifyCollectionChangedAction.Remove:\n                    if (args.OldStartingIndex < 0)\n                        throw new InvalidOperationException(SR.RemovedItemNotFound);\n\n                    for (int i = args.OldItems.Count - 1, index = args.OldStartingIndex + i; i >= 0; --i, --index)\n                    {\n                        if (!System.Windows.Controls.ItemsControl.EqualsEx(args.OldItems[i], _snapshot[index]))\n                            // replace error message with a better one\n                            throw new InvalidOperationException(SR.Format(SR.AddedItemNotAtIndex, index));\n                        _snapshot.RemoveAt(index);\n                    }\n                    break;\n\n                case NotifyCollectionChangedAction.Replace:\n                    for (int i = args.NewItems.Count - 1, index = args.NewStartingIndex + i; i >= 0; --i, --index)\n                    {\n                        if (!System.Windows.Controls.ItemsControl.EqualsEx(args.OldItems[i], _snapshot[index]))\n                            // replace error message with a better one\n                            throw new InvalidOperationException(SR.Format(SR.AddedItemNotAtIndex, index));\n                        _snapshot[index] = args.NewItems[i];\n                    }","sourceCodeStart":402,"sourceCodeEnd":438,"githubUrl":"https://github.com/dotnet/wpf/blob/81131a70a4c573cd62748a5c36908fc4d662daa9/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/Data/EnumerableCollectionView.cs#L402-L438","documentation":"EnumerableCollectionView maintains an internal _snapshot of the source collection. On a Remove notification with no OldStartingIndex (index < 0), the view cannot locate which snapshot entry to remove, so it throws RemovedItemNotFound. This is an internal-invariant check: the INotifyCollectionChanged contract should always supply OldStartingIndex for Remove actions.","triggerScenarios":"A source collection implementing INotifyCollectionChanged raises CollectionChanged with Action=Remove and OldStartingIndex = -1 (or omits the index), which EnumerableCollectionView processes via ProcessCollectionChanged.","commonSituations":"Using a custom or third-party ObservableCollection-like class that fires Reset-style Remove events with unspecified indices, violating the INotifyCollectionChanged contract while bound to an ItemsControl.","solutions":["Fix the source collection to always set OldStartingIndex (and OldItems) when raising NotifyCollectionChangedAction.Remove.","Raise NotifyCollectionChangedAction.Reset instead of an index-less Remove for bulk/unindexed changes.","Use ObservableCollection<T> (or a correct implementation) as the source instead of the custom collection.","Unbind/rebind (refresh the view) if the snapshot has already diverged."],"exampleFix":"// before\nRaiseCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item));\n// after\nint idx = IndexOf(item);\nRaiseCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, idx));","handlingStrategy":"validation","validationCode":"// validate Remove args before raising\nif (action == NotifyCollectionChangedAction.Remove && args.OldStartingIndex < 0)\n    throw new InvalidOperationException(\"Remove events must supply OldStartingIndex\");","typeGuard":null,"tryCatchPattern":"// wrap view refresh\ntry { view.Refresh(); } catch (InvalidOperationException) { view.Refresh(); }","preventionTips":["Always set OldStartingIndex and OldItems on Remove events.","Prefer deriving custom collections from ObservableCollection<T>.","Use Reset for unindexed bulk removals.","Unit-test your INotifyCollectionChanged event payloads."],"tags":["wpf","inotifycollectionchanged","collection-view","contract-violation"],"backgroundTag":"internal-invariant-violation","analyzedSha":"81131a70a4c573cd62748a5c36908fc4d662daa9","analyzedAt":"2026-09-14T10:12:48.479Z","contentChangedAt":"2026-09-14T10:12:48.479Z","schemaVersion":2},"datasetVersion":"2026-09-21T21:30:21.729Z"}