dotnet/wpf · error · NotSupportedException
SR.Format(SR.UnexpectedCollectionChangeAction, args.Action)
Error message
SR.Format(SR.UnexpectedCollectionChangeAction, args.Action)
What it means
ItemContainerGenerator's collection-changed handler switches over NotifyCollectionChangedAction and handles Add, Remove, Replace, Move, and Reset. Any other value of the enum (or an invalid cast value from a misbehaving collection) falls through to the default case, which throws NotSupportedException with 'UnexpectedCollectionChangeAction'. This is effectively an internal-invariant guard: the WPF generator simply has no code path for the action it received.
Solutions
- Fix the source collection so it only raises valid NotifyCollectionChangedAction values (Add, Remove, Replace, Move, Reset).
- Use NotifyCollectionChangedEventArgs' public static factory methods (Add/Remove/Replace/Move/Reset) instead of constructing the event args manually.
- Use ObservableCollection<T> to guarantee well-formed change notifications.
- Audit third-party collection libraries for versions that emit custom/invalid action values.
Example fix
// before var e = new NotifyCollectionChangedEventArgs((NotifyCollectionChangedAction)99); // after var e = NotifyCollectionChangedEventArgs.Reset(); // or Add/Remove/Replace/Move factory methods
Defensive patterns
Strategy: validation
Validate before calling
if (!Enum.IsDefined(typeof(NotifyCollectionChangedAction), args.Action))
throw new InvalidOperationException("Invalid NotifyCollectionChangedAction value"); Type guard
bool IsValidAction(NotifyCollectionChangedAction a) =>
a is NotifyCollectionChangedAction.Add
or NotifyCollectionChangedAction.Remove
or NotifyCollectionChangedAction.Replace
or NotifyCollectionChangedAction.Move
or NotifyCollectionChangedAction.Reset; Try / catch
try { SubscribeGenerator(collection); }
catch (NotSupportedException ex) when (ex.Message.Contains("UnexpectedCollectionChangeAction"))
{
// collection emits malformed events; resubscribe to a sanitized wrapper
SubscribeGenerator(new SanitizingObservableCollection(collection));
} Prevention
- Use NotifyCollectionChangedEventArgs static factory methods instead of raw constructors
- Validate action values in custom collection event-raising code
- Do not cast arbitrary ints to NotifyCollectionChangedAction
- Pin third-party collection library versions known to emit standard actions
When it happens
Trigger: A custom INotifyCollectionChanged collection raising a NotifyCollectionChangedEventArgs with an out-of-range/undefined NotifyCollectionChangedAction value (e.g. cast of an invalid int), or a collection passing an action the generator does not recognize after the known cases.
Common situations: Hand-rolled or third-party observable collections that construct NotifyCollectionChangedEventArgs manually with an unhandled or corrupted action value; binary-incompatible libraries emitting new/unknown action values into older WPF.
Related errors
- ' ' can only host a ' ' or a ' '. ' ' is an invalid…
- A panel with IsItemsHost="true" is not nested in an…
- Cannot explicitly modify Children collection of Panel used…
- FileAccess value is not valid.
- Invalid priority value.
AI-assisted analysis of dotnet/wpf@81131a70a4 (2026-09-14).
Data as JSON: /api/errors/cfc8364dd619a9c0.
Report an issue: GitHub.
Appendix: source
Thrown at src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/ItemContainerGenerator.cs:2427
OnItemReplaced(args.OldItems[0], args.NewItems[0], args.NewStartingIndex);
break;
case NotifyCollectionChangedAction.Move:
// Don't check arguments if app targets 4.0, for compat ( 726682)
if (!FrameworkCompatibilityPreferences.TargetsDesktop_V4_0)
{
if (args.OldItems.Count != 1)
throw new NotSupportedException(SR.RangeActionsNotSupported);
}
OnItemMoved(args.OldItems[0], args.OldStartingIndex, args.NewStartingIndex);
break;
case NotifyCollectionChangedAction.Reset:
OnRefresh();
break;
default:
throw new NotSupportedException(SR.Format(SR.UnexpectedCollectionChangeAction, args.Action));
}
PresentationTraceLevel traceLevel = PresentationTraceSources.GetTraceLevel(this);
if (traceLevel >= PresentationTraceLevel.High)
{
Verify();
}
}
// Called when an item is added to the items collection
private void OnItemAdded(object item, int index)
{
if (_itemMap == null)
{
// reentrant call (from RemoveAllInternal) shouldn't happen,
// but if it does, don't crash
Debug.Fail("unexpected reentrant call to OnItemAdded");
return;View on GitHub (pinned to 81131a70a4)