dotnet/wpf · error · InvalidOperationException

SR.Format(SR.CannotRemoveUnrealizedItems, index, count)

Error message

SR.Format(SR.CannotRemoveUnrealizedItems, index, count)

What it means

Remove/Recycle walks the item map blocks from the given position and requires every block in the removal range to be a RealizedItemBlock. If it encounters an unrealized block within the range, the generator throws InvalidOperationException (CannotRemoveUnrealizedItems) because unrealized items have no containers to remove or recycle.

Solutions

  1. Ensure the removal range contains only realized items: check ContainerFromIndex/ShouldRealize before including an item.
  2. Recalculate the position and count against the current item map after any CollectionChanged event.
  3. Use Recycle semantics only on containers you actually obtained from GenerateNext in this pass.

Example fix

// before
generator.Remove(startPos, pageItemCount, true); // range may include unrealized items

// after
int realized = 0;
while (realized < pageItemCount && generator.ContainerFromIndex(firstIndex + realized) != null)
    realized++;
if (realized > 0)
    generator.Remove(startPos, realized, true);
Defensive patterns

Strategy: validation

Validate before calling

int realizedCount = 0;
for (int i = firstIndex; i <= lastIndex; i++)
    if (generator.ContainerFromIndex(i) != null) realizedCount++;
if (realizedCount != lastIndex - firstIndex + 1)
    throw new InvalidOperationException("Range includes unrealized items; cannot Remove");

Type guard

static bool AllRealized(ItemContainerGenerator g, int from, int count) =>
    Enumerable.Range(from, count).All(i => g.ContainerFromIndex(i) != null);

Try / catch

try { generator.Remove(pos, count, true); }
catch (InvalidOperationException) { /* some items unrealized: re-derive range */ }

Prevention

When it happens

Trigger: Calling Remove(position, count, ...) where the range spans unrealized (not yet generated) items — e.g. removing a batch larger than the realized region, or using a stale index after items were virtualized away.

Common situations: Custom VirtualizingPanels recycling a viewport range after a collection change shifted indices; removing a whole page of items when only part was ever generated.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of dotnet/wpf@81131a70a4 (2026-09-14). Data as JSON: /api/errors/8e13db3db5c0f5bf. Report an issue: GitHub.

Appendix: source

Thrown at src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/ItemContainerGenerator.cs:302

            ItemBlock block;

            // find the leftmost item to remove
            int offsetL = index;
            for (block = _itemMap.Next;  block != _itemMap;  block = block.Next)
            {
                if (offsetL < block.ContainerCount)
                    break;

                offsetL -= block.ContainerCount;
            }
            RealizedItemBlock blockL = block as RealizedItemBlock;

            // find the rightmost item to remove
            int offsetR = offsetL + count - 1;
            for (; block != _itemMap;  block = block.Next)
            {
                if (!(block is RealizedItemBlock))
                    throw new InvalidOperationException(SR.Format(SR.CannotRemoveUnrealizedItems, index, count));

                if (offsetR < block.ContainerCount)
                    break;

                offsetR -= block.ContainerCount;
            }
            RealizedItemBlock blockR = block as RealizedItemBlock;

            // de-initialize the containers that are being removed
            RealizedItemBlock rblock = blockL;
            int offset = offsetL;
            while (rblock != blockR || offset <= offsetR)
            {
                DependencyObject container = rblock.ContainerAt(offset);

                UnlinkContainerFromItem(container, rblock.ItemAt(offset));
                // DataGrid generates non-GroupItem for NewItemPlaceHolder
                // Dont recycle in this case.

View on GitHub (pinned to 81131a70a4)