dotnet/wpf · error · InvalidOperationException

throw new InvalidOperationException(SR.RemovedItemNotFound);

Error message

throw new InvalidOperationException(SR.RemovedItemNotFound);

What it means

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.

Solutions

  1. Fix the source collection to always set OldStartingIndex (and OldItems) when raising NotifyCollectionChangedAction.Remove.
  2. Raise NotifyCollectionChangedAction.Reset instead of an index-less Remove for bulk/unindexed changes.
  3. Use ObservableCollection<T> (or a correct implementation) as the source instead of the custom collection.
  4. Unbind/rebind (refresh the view) if the snapshot has already diverged.

Example fix

// before
RaiseCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item));
// after
int idx = IndexOf(item);
RaiseCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, idx));
Defensive patterns

Strategy: validation

Validate before calling

// validate Remove args before raising
if (action == NotifyCollectionChangedAction.Remove && args.OldStartingIndex < 0)
    throw new InvalidOperationException("Remove events must supply OldStartingIndex");

Try / catch

// wrap view refresh
try { view.Refresh(); } catch (InvalidOperationException) { view.Refresh(); }

Prevention

When it happens

Trigger: A source collection implementing INotifyCollectionChanged raises CollectionChanged with Action=Remove and OldStartingIndex = -1 (or omits the index), which EnumerableCollectionView processes via ProcessCollectionChanged.

Common situations: 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.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of dotnet/wpf@81131a70a4 (2026-09-14). Data as JSON: /api/errors/ee8c7140c8a01127. Report an issue: GitHub.

Appendix: source

Thrown at src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/Data/EnumerableCollectionView.cs:420

                    if (args.NewStartingIndex < 0 || _snapshot.Count <= args.NewStartingIndex)
                    {   // append
                        for (int i = 0; i < args.NewItems.Count; ++i)
                        {
                            _snapshot.Add(args.NewItems[i]);
                        }
                    }
                    else
                    {   // insert
                        for (int i = args.NewItems.Count - 1; i >= 0; --i)
                        {
                            _snapshot.Insert(args.NewStartingIndex, args.NewItems[i]);
                        }
                    }
                    break;

                case NotifyCollectionChangedAction.Remove:
                    if (args.OldStartingIndex < 0)
                        throw new InvalidOperationException(SR.RemovedItemNotFound);

                    for (int i = args.OldItems.Count - 1, index = args.OldStartingIndex + i; i >= 0; --i, --index)
                    {
                        if (!System.Windows.Controls.ItemsControl.EqualsEx(args.OldItems[i], _snapshot[index]))
                            // replace error message with a better one
                            throw new InvalidOperationException(SR.Format(SR.AddedItemNotAtIndex, index));
                        _snapshot.RemoveAt(index);
                    }
                    break;

                case NotifyCollectionChangedAction.Replace:
                    for (int i = args.NewItems.Count - 1, index = args.NewStartingIndex + i; i >= 0; --i, --index)
                    {
                        if (!System.Windows.Controls.ItemsControl.EqualsEx(args.OldItems[i], _snapshot[index]))
                            // replace error message with a better one
                            throw new InvalidOperationException(SR.Format(SR.AddedItemNotAtIndex, index));
                        _snapshot[index] = args.NewItems[i];
                    }

View on GitHub (pinned to 81131a70a4)