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
- 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.
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
- 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.
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
- SR.AddedItemNotAtIndex (formatted with index)
- SR.AddedItemNotInCollection
- SR.BindingListCannotCustomFilter
- SR.BindingListCanOnlySortByOneProperty
- SR.CannotMoveToUnknownPosition
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)