{"record":{"id":"ed565567c04400e7","repo":"dotnet/wpf","slug":"sr-collectionchangeindexoutofrange-formatted-with-index","errorCode":null,"errorMessage":"SR.CollectionChangeIndexOutOfRange (formatted with index, ilFull.Count)","messagePattern":"SR\\.CollectionChangeIndexOutOfRange \\(formatted with index, ilFull\\.Count\\)","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Data/ListCollectionView.cs","lineNumber":2666,"sourceCode":"                // and only when the placeholder is active.\n                // In that case the item's index in the view is 1 when the placeholder\n                // is AtBeginning, and just before the placeholder when it's AtEnd.\n                // The numerical value for the latter case dependds on whether there's\n                // a sort/filter or not, i.e. whether we're using a local array.  That's\n                // because the item has already been removed from the collection, but\n                // not from the local array.  (DDB 201860)\n                return (NewItemPlaceholderPosition == NewItemPlaceholderPosition.AtBeginning)\n                        ? 1 : UsesLocalArray ? InternalCount - 2 : index;\n            }\n\n            int delta = IsGrouping ? 0 :\n                        (NewItemPlaceholderPosition == NewItemPlaceholderPosition.AtBeginning)\n                        ? (IsAddingNew ? 2 : 1) : 0;\n            IList ilFull = (AllowsCrossThreadChanges ? ShadowCollection : SourceCollection) as IList;\n\n            // validate input\n            if (index < -1 || index > ilFull.Count)\n                throw new InvalidOperationException(SR.Format(SR.CollectionChangeIndexOutOfRange, index, ilFull.Count));\n\n            if (action == NotifyCollectionChangedAction.Add)\n            {\n                if (index >= 0)\n                {\n                    if (!System.Windows.Controls.ItemsControl.EqualsEx(item, ilFull[index]))\n                        throw new InvalidOperationException(SR.Format(SR.AddedItemNotAtIndex, index));\n                }\n                else\n                {\n                    // event didn't specify index - determine it the hard way\n                    index = ilFull.IndexOf(item);\n                    if (index < 0)\n                        throw new InvalidOperationException(SR.AddedItemNotInCollection);\n                }\n            }\n\n            // if there's no sort or filter, use the index into the full array","sourceCodeStart":2648,"sourceCodeEnd":2684,"githubUrl":"https://github.com/dotnet/wpf/blob/81131a70a4c573cd62748a5c36908fc4d662daa9/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Data/ListCollectionView.cs#L2648-L2684","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before (index raised before insert)\nOnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index));\nItems.Insert(index, item);\n// after\nItems.Insert(index, item);\nOnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index));","handlingStrategy":"validation","validationCode":"int index = args.NewStartingIndex;\nif (index != -1 && (index < 0 || index > sourceList.Count)) { /* invalid; raise Reset instead */ }","typeGuard":null,"tryCatchPattern":"try { /* update UI state from event */ } catch (InvalidOperationException) { view.Refresh(); }","preventionTips":["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"],"tags":["wpf","collection-view","index-out-of-range"],"backgroundTag":"index-out-of-range","analyzedSha":"81131a70a4c573cd62748a5c36908fc4d662daa9","analyzedAt":"2026-09-14T10:12:48.479Z","contentChangedAt":"2026-09-14T10:12:48.479Z","schemaVersion":2},"datasetVersion":"2026-09-21T21:30:21.729Z"}