dotnet/wpf · error · InvalidOperationException
throw new…
Error message
throw new InvalidOperationException(SR.Format(SR.AddedItemNotAtIndex, oldIndex));
What it means
While applying a Move where the item moved forward (OldStartingIndex < NewStartingIndex), the view walks backwards verifying each moved item against its snapshot at the old index. A mismatch between args.OldItems[i] and _snapshot[oldIndex] throws AddedItemNotAtIndex because the snapshot is out of sync with the source.
Solutions
- Verify the Move implementation raises the event with correct OldItems and indices before any actual mutation side effects skew them.
- Raise Reset instead of Move when precise indices can't be guaranteed.
- Avoid mutating the collection without raising notifications.
- Refresh/rebind the view to resynchronize the snapshot.
Example fix
// before _items.Move(oldIdx, newIdx); // but event raised with wrong OldItems // after var moved = _items[oldIdx]; RaiseEvent(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move, moved, newIdx, oldIdx)); _items.Move(oldIdx, newIdx);
Defensive patterns
Strategy: validation
Validate before calling
// confirm snapshot consistency before raising Move
if (!Equals(snapshot[oldIdx], movedItem))
raise Reset instead of Move; Try / catch
try { ApplyMove(args); } catch (InvalidOperationException) { view.Refresh(); } Prevention
- Raise Move events with items exactly as they existed pre-move.
- Pair every mutation with a notification.
- Avoid Remove+Add masquerading as Move.
- Refresh the view after bulk reorders.
When it happens
Trigger: A Move event (moving items toward higher indices) whose OldItems don't match what the view previously recorded at OldStartingIndex..OldStartingIndex+Count-1.
Common situations: Source collection reordered itself before raising the Move event, or interleaved unreported Add/Remove calls that shifted indices; custom collection with buggy Move implementation.
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
- throw new…
- throw new…
- SR.Format(SR.UnexpectedCollectionChangeAction, args.Action)
- SR.Format(SR.UnexpectedCollectionChangeAction, e.Action)
- SR.RangeActionsNotSupported
AI-assisted analysis of dotnet/wpf@81131a70a4 (2026-09-14).
Data as JSON: /api/errors/73b8332681a99189.
Report an issue: GitHub.
Appendix: source
Thrown at src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/Data/EnumerableCollectionView.cs:455
_snapshot[index] = args.NewItems[i];
}
break;
case NotifyCollectionChangedAction.Move:
if (args.NewStartingIndex < 0)
throw new InvalidOperationException(SR.CannotMoveToUnknownPosition);
if (args.OldStartingIndex < args.NewStartingIndex)
{
for (int i = args.OldItems.Count - 1,
oldIndex = args.OldStartingIndex + i,
newIndex = args.NewStartingIndex + i;
i >= 0;
--i, --oldIndex, --newIndex)
{
if (!System.Windows.Controls.ItemsControl.EqualsEx(args.OldItems[i], _snapshot[oldIndex]))
// replace error message with a better one
throw new InvalidOperationException(SR.Format(SR.AddedItemNotAtIndex, oldIndex));
_snapshot.Move(oldIndex, newIndex);
}
}
else
{
for (int i = 0,
oldIndex = args.OldStartingIndex + i,
newIndex = args.NewStartingIndex + i;
i < args.OldItems.Count;
++i, ++oldIndex, ++newIndex)
{
if (!System.Windows.Controls.ItemsControl.EqualsEx(args.OldItems[i], _snapshot[oldIndex]))
// replace error message with a better one
throw new InvalidOperationException(SR.Format(SR.AddedItemNotAtIndex, oldIndex));
_snapshot.Move(oldIndex, newIndex);
}
}
View on GitHub (pinned to 81131a70a4)