dotnet/maui · error · ArgumentException

Index '{Math.Max(e.NewStartingIndex, e.OldStartingIndex)}' i

Error message

Index '{Math.Max(e.NewStartingIndex, e.OldStartingIndex)}' is greater than the number of rows '{lastIndex}'.

What it means

ListViewRenderer's CellsOnCollectionChanged validates that a NotifyCollectionChangedEventArgs index (NewStartingIndex/OldStartingIndex) does not exceed the UITableView's current row count for the section. This only fires for non-grouped, RetainElement lists. The check exists because iOS UITableView will crash if asked to insert/update rows past its reported count - so MAUI surfaces a clearer ArgumentException first.

Source

Thrown at src/Compatibility/Core/src/iOS/Renderers/ListViewRenderer.cs:568

		{
			var exArgs = e as NotifyCollectionChangedEventArgsEx;
			if (exArgs != null)
				_dataSource.Counts[section] = exArgs.Count;

			// This means the UITableView hasn't rendered any cells yet
			// so there's no need to synchronize the rows on the UITableView
			if (Control.IndexPathsForVisibleRows == null && e.Action != NotifyCollectionChangedAction.Reset)
				return;

			var groupReset = resetWhenGrouped && Element.IsGroupingEnabled;

			// We can't do this check on grouped lists because the index doesn't match the number of rows in a section.
			// 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.
			if (!groupReset && Element.CachingStrategy == ListViewCachingStrategy.RetainElement)
			{
				var lastIndex = Control.NumberOfRowsInSection(section);
				if (e.NewStartingIndex > lastIndex || e.OldStartingIndex > lastIndex)
					throw new ArgumentException(
						$"Index '{Math.Max(e.NewStartingIndex, e.OldStartingIndex)}' is greater than the number of rows '{lastIndex}'.");
			}

			switch (e.Action)
			{
				case NotifyCollectionChangedAction.Add:
					if (e.NewStartingIndex == -1 || groupReset)
						goto case NotifyCollectionChangedAction.Reset;

					InsertRows(e.NewStartingIndex, e.NewItems.Count, section);

					break;

				case NotifyCollectionChangedAction.Remove:
					if (e.OldStartingIndex == -1 || groupReset)
						goto case NotifyCollectionChangedAction.Reset;

					DeleteRows(e.OldStartingIndex, e.OldItems.Count, section);

View on GitHub (pinned to f377ff1c5e)

Solutions

  1. Always mutate the bound ObservableCollection on the main UI thread.
  2. For bulk edits, use a single Reset notification or replace the entire ItemsSource rather than per-item Add with indices.
  3. Switch the ListView to RecycleElement (the check is skipped for that strategy) if appropriate.
  4. If raising collection changes manually, ensure NewStartingIndex/OldStartingIndex are accurate and within bounds.

Example fix

// before
_items.Add(item); // fired from background thread with stale index

// after
Device.BeginInvokeOnMainThread(() => _items.Add(item));
// or for bulk:
_items = new ObservableCollection<Item>(newItems);
list.ItemsSource = _items;
Defensive patterns

Strategy: validation

Validate before calling

// Ensure all ItemsSource mutations occur on the main thread:
Device.BeginInvokeOnMainThread(() => collection.Add(item));

Try / catch

try { /* navigation/mutation */ }
catch (ArgumentException ex) when (ex.Message.Contains("number of rows"))
{
    // collection state desynced; refresh the source
    list.ItemsSource = null;
    list.ItemsSource = collection;
}

Prevention

When it happens

Trigger: An ObservableCollection bound to ListView.ItemsSource raises a collection-changed event with a NewStartingIndex or OldStartingIndex larger than the number of rows currently in that section. Common with batched/range edits, multi-threaded mutation, or an incorrect NotifyCollectionChangedEventArgs constructed manually.

Common situations: Calling CollectionChanged with Reset-vs-Add semantics incorrectly; mutating the source collection off the main thread; replacing the ItemsSource while a pending change is in flight; using RetainElement with a source that reports stale indices.

Related errors


AI-assisted analysis of dotnet/maui@f377ff1c5e (2026-08-13). Data as JSON: /api/errors/644fc6283ca1427e. Report an issue: GitHub.