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
- Recycle containers of only one type per generator session; recycle heterogeneous containers individually without mixing them in the same recycle batch.
- Return containers of a consistent type from PrepareContainerForItemOverride/SelectTemplate, or disable container recycling (VirtualizationMode.Standard instead of Recycling).
- 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
- Keep container types uniform per ItemsControl (avoid template selectors that change the container CLR type).
- Use VirtualizationMode.Standard when containers are heterogeneous.
- Group recycle batches by container type.
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
- ' ' is not a Visual or Visual3D.
- 0x80070057
- Animation_AnimationTimelineTypeMismatch
- Property data must be a non-reference variant compatible…
- Property is not a valid instance of PrintTicket.
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)