{"record":{"id":"23268ee5c43b4fd9","repo":"dotnet/maui","slug":"index-math-max-e-newstartingindex-e-oldstarting-23268e","errorCode":null,"errorMessage":"Index '{Math.Max(e.NewStartingIndex, e.OldStartingIndex)}' is greater than the number of rows '{lastIndex}'.","messagePattern":"Index '(.+?)' is greater than the number of rows '(.+?)'\\.","errorType":"exception","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"src/Controls/src/Core/Compatibility/Handlers/ListView/iOS/ListViewRenderer.cs","lineNumber":590,"sourceCode":"\t\t{\n\t\t\tvar exArgs = e as NotifyCollectionChangedEventArgsEx;\n\t\t\tif (exArgs != null)\n\t\t\t\t_dataSource.Counts[section] = exArgs.Count;\n\n\t\t\t// This means the UITableView hasn't rendered any cells yet\n\t\t\t// so there's no need to synchronize the rows on the UITableView\n\t\t\tif (Control.IndexPathsForVisibleRows == null && e.Action != NotifyCollectionChangedAction.Reset)\n\t\t\t\treturn;\n\n\t\t\tvar groupReset = resetWhenGrouped && Element.IsGroupingEnabled;\n\n\t\t\t// We can't do this check on grouped lists because the index doesn't match the number of rows in a section.\n\t\t\t// Likewise, we can't do this check on lists using RecycleElement because the number of rows in a section will remain constant because they are reused.\n\t\t\tif (!groupReset && Element.CachingStrategy == ListViewCachingStrategy.RetainElement)\n\t\t\t{\n\t\t\t\tvar lastIndex = Control.NumberOfRowsInSection(section);\n\t\t\t\tif (e.NewStartingIndex > lastIndex || e.OldStartingIndex > lastIndex)\n\t\t\t\t\tthrow new ArgumentException(\n\t\t\t\t\t\t$\"Index '{Math.Max(e.NewStartingIndex, e.OldStartingIndex)}' is greater than the number of rows '{lastIndex}'.\");\n\t\t\t}\n\n\t\t\tswitch (e.Action)\n\t\t\t{\n\t\t\t\tcase NotifyCollectionChangedAction.Add:\n\t\t\t\t\tif (e.NewStartingIndex == -1 || groupReset)\n\t\t\t\t\t\tgoto case NotifyCollectionChangedAction.Reset;\n\n\t\t\t\t\tInsertRows(e.NewStartingIndex, e.NewItems.Count, section);\n\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase NotifyCollectionChangedAction.Remove:\n\t\t\t\t\tif (e.OldStartingIndex == -1 || groupReset)\n\t\t\t\t\t\tgoto case NotifyCollectionChangedAction.Reset;\n\n\t\t\t\t\tDeleteRows(e.OldStartingIndex, e.OldItems.Count, section);","sourceCodeStart":572,"sourceCodeEnd":608,"githubUrl":"https://github.com/dotnet/maui/blob/f377ff1c5ee04d334d8a925f50c83a6b7afddf03/src/Controls/src/Core/Compatibility/Handlers/ListView/iOS/ListViewRenderer.cs#L572-L608","documentation":"Thrown by the iOS ListViewRenderer when a NotifyCollectionChangedEventArgs reports a NewStartingIndex or OldStartingIndex greater than the current number of rows in the section (only checked for RetainElement strategy and non-grouped lists). This indicates the bound ObservableCollection raised a change event whose index is inconsistent with the actual row count.","triggerScenarios":"An ObservableCollection<T> raises CollectionChanged with a starting index that exceeds the section's row count. Manipulating the underlying list from a background thread (cross-thread mutation). Calling collection.Move/Insert with a stale index. A custom INotifyCollectionChanged implementation emitting bad indices.","commonSituations":"Mutating the bound collection off the main thread. Batch operations that remove and re-add with mismatched indices. Wrapping a list with a custom notifier that miscalculates indices. Race between source mutation and renderer measurement.","solutions":["Marshal all collection mutations to the main/UI thread.","Ensure INotifyCollectionChanged indices always match the post-mutation list state (use standard ObservableCollection).","Prefer ListViewCachingStrategy.RecycleElement or grouping if index semantics differ, but first fix the source of bad indices.","Validate that NewStartingIndex/OldStartingIndex are within [0, Count] before raising CollectionChanged in custom collections."],"exampleFix":"// before — mutating off thread\ntask.ContinueWith(_ => observableCollection.Insert(10, item));\n\n// after\nMainThread.BeginInvokeOnMainThread(() => observableCollection.Insert(index, item));","handlingStrategy":"validation","validationCode":"// Marshal collection mutations to the main thread and validate indices\nMainThread.BeginInvokeOnMainThread(() =>\n{\n    if (index >= 0 && index <= collection.Count)\n        collection.Insert(index, item);\n});","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always mutate the bound ObservableCollection on the main thread.","Use standard ObservableCollection to guarantee correct indices.","Validate NewStartingIndex/OldStartingIndex against Count in custom collections."],"tags":["maui","listview","ios","observablecollection","threading","index"],"backgroundTag":null,"analyzedSha":"f377ff1c5ee04d334d8a925f50c83a6b7afddf03","analyzedAt":"2026-08-13T14:26:18.069Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}