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

  1. Mutate the collection FIRST, then raise the CollectionChanged event with the final index.
  2. Ensure the index passed to NotifyCollectionChangedEventArgs is within [0, Count] of the current list.
  3. If the index is unreliable, pass -1 (unknown) so the view computes it, or raise Reset.
  4. 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

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


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 array

View on GitHub (pinned to 81131a70a4)