dotnet/wpf · error · InvalidOperationException
SR.CollectionChangeIndexOutOfRange (formatted with index…
Error message
SR.CollectionChangeIndexOutOfRange (formatted with index, ilFull.Count)
What it means
During ItemsControl's internal collection-change verification (ProcessCollectionChange verification path), the reported index into the source list is negative (other than -1) or beyond the list's item count. The framework throws InvalidOperationException with SR.CollectionChangeIndexOutOfRange including the index and the valid count.
Solutions
- Mutate the collection FIRST, then raise the CollectionChanged event with the final index.
- Ensure the index passed to NotifyCollectionChangedEventArgs is within [0, Count] of the current list.
- If the index is unreliable, pass -1 (unknown) so the view computes it, or raise Reset.
- Avoid mutating the source collection off the UI thread while bound to an ItemsControl.
Example fix
// before (index raised before insert) OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index)); Items.Insert(index, item); // after Items.Insert(index, item); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index));
Defensive patterns
Strategy: validation
Validate before calling
int index = args.NewStartingIndex;
if (index != -1 && (index < 0 || index > sourceList.Count)) { /* invalid; raise Reset instead */ } Try / catch
try { /* update UI state from event */ } catch (InvalidOperationException) { view.Refresh(); } Prevention
- Mutate the list first, then raise the event with the final index
- Compute indices against the live list, not a stale snapshot
- Keep all mutations on the UI thread for bound collections
When it happens
Trigger: A CollectionChanged event (e.g. Add/Replace/Remove) declares a NewStartingIndex/OldStartingIndex that doesn't fit within [0, ilFull.Count] of the actual source collection, so the index validation `index < -1 || index > ilFull.Count` fails.
Common situations: Custom collections raising events before actually mutating the list (index not yet valid), index computed against a different snapshot, off-by-one in manual event raising, cross-thread mutation of the source between event raise and handling.
Related errors
- IndexOutOfRangeException
- IndexOutOfRangeException
- SR.AddedItemNotAtIndex (formatted with index)
- SR.AddedItemNotInCollection
- SR.BindingListCannotCustomFilter
AI-assisted analysis of dotnet/wpf@81131a70a4 (2026-09-14).
Data as JSON: /api/errors/ed565567c04400e7.
Report an issue: GitHub.
Appendix: source
Thrown at src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Data/ListCollectionView.cs:2666
// and only when the placeholder is active.
// In that case the item's index in the view is 1 when the placeholder
// is AtBeginning, and just before the placeholder when it's AtEnd.
// The numerical value for the latter case dependds on whether there's
// a sort/filter or not, i.e. whether we're using a local array. That's
// because the item has already been removed from the collection, but
// not from the local array. (DDB 201860)
return (NewItemPlaceholderPosition == NewItemPlaceholderPosition.AtBeginning)
? 1 : UsesLocalArray ? InternalCount - 2 : index;
}
int delta = IsGrouping ? 0 :
(NewItemPlaceholderPosition == NewItemPlaceholderPosition.AtBeginning)
? (IsAddingNew ? 2 : 1) : 0;
IList ilFull = (AllowsCrossThreadChanges ? ShadowCollection : SourceCollection) as IList;
// validate input
if (index < -1 || index > ilFull.Count)
throw new InvalidOperationException(SR.Format(SR.CollectionChangeIndexOutOfRange, index, ilFull.Count));
if (action == NotifyCollectionChangedAction.Add)
{
if (index >= 0)
{
if (!System.Windows.Controls.ItemsControl.EqualsEx(item, ilFull[index]))
throw new InvalidOperationException(SR.Format(SR.AddedItemNotAtIndex, index));
}
else
{
// event didn't specify index - determine it the hard way
index = ilFull.IndexOf(item);
if (index < 0)
throw new InvalidOperationException(SR.AddedItemNotInCollection);
}
}
// if there's no sort or filter, use the index into the full arrayView on GitHub (pinned to 81131a70a4)