dotnet/wpf · error · InvalidOperationException

SR.CannotRecyleHeterogeneousTypes

Error message

SR.CannotRecyleHeterogeneousTypes

What it means

When recycling generated containers, ItemContainerGenerator keeps the type of the first recycled container and enqueues only containers of that same type. If a container of a different CLR type is offered for recycling, it throws InvalidOperationException (CannotRecyleHeterogeneousTypes) because recycled containers are pooled by type and mixing types would produce wrong containers on reuse.

Solutions

  1. Recycle containers of only one type per generator session; recycle heterogeneous containers individually without mixing them in the same recycle batch.
  2. Return containers of a consistent type from PrepareContainerForItemOverride/SelectTemplate, or disable container recycling (VirtualizationMode.Standard instead of Recycling).
  3. Filter the recycle range to containers of the established _containerType before calling Recycle.

Example fix

// before
foreach (var c in realizedContainers)
    generator.Recycle(pos, 1, true); // mixes types -> throws

// after
Type firstType = realizedContainers[0].GetType();
foreach (var c in realizedContainers.Where(c => c.GetType() == firstType))
    generator.Recycle(posFor(c), 1, true);
Defensive patterns

Strategy: validation

Validate before calling

var types = containers.Select(c => c.GetType()).Distinct().ToList();
if (types.Count > 1)
    throw new InvalidOperationException($"Mixed container types: {string.Join(",", types)}"); // recycle per-type instead

Type guard

static bool IsHomogeneous(IEnumerable<DependencyObject> cs) =>
    cs.Select(c => c.GetType()).Distinct().Count() <= 1;

Try / catch

try { generator.Recycle(pos, count, true); }
catch (InvalidOperationException) { /* fall back: remove and regenerate instead of recycling */ }

Prevention

When it happens

Trigger: Calling Recycle on a range whose realized containers come from different DataTemplates/type overrides (e.g. ListBoxItem vs derived type, or ContentPresenter containers of different types) — common with heterogeneous item templates or ItemContainerStyleSelector.

Common situations: Hierarchical or templated lists (TreeView/ItemsControl with DataTemplateSelector) where sibling items resolve to different container types; custom panels recycling across type boundaries.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


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

Appendix: source

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

            {
                DependencyObject container = rblock.ContainerAt(offset);

                UnlinkContainerFromItem(container, rblock.ItemAt(offset));
                // DataGrid generates non-GroupItem for NewItemPlaceHolder
                // Dont recycle in this case.
                bool isNewItemPlaceHolderWhenGrouping = _generatesGroupItems && !(container is GroupItem);

                if (isRecycling && !isNewItemPlaceHolderWhenGrouping)
                {
                    Debug.Assert(!_recyclableContainers.Contains(container), "trying to add a container to the collection twice");

                    if (_containerType == null)
                    {
                        _containerType = container.GetType();
                    }
                    else if (_containerType != container.GetType())
                    {
                        throw new InvalidOperationException(SR.CannotRecyleHeterogeneousTypes);
                    }

                    _recyclableContainers.Enqueue(container);
                }

                if (++offset >= rblock.ContainerCount && rblock != blockR)
                {
                    rblock = rblock.Next as RealizedItemBlock;
                    offset = 0;
                }
            }

            // see whether the range hits the edge of a block on either side,
            // and whether the a`butting block is an unrealized gap
            bool edgeL = (offsetL == 0);
            bool edgeR = (offsetR == blockR.ItemCount-1);
            bool abutL = edgeL && (blockL.Prev is UnrealizedItemBlock);
            bool abutR = edgeR && (blockR.Next is UnrealizedItemBlock);

View on GitHub (pinned to 81131a70a4)