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

  1. Fix the source collection so it only raises valid NotifyCollectionChangedAction values (Add, Remove, Replace, Move, Reset).
  2. Use NotifyCollectionChangedEventArgs' public static factory methods (Add/Remove/Replace/Move/Reset) instead of constructing the event args manually.
  3. Use ObservableCollection<T> to guarantee well-formed change notifications.
  4. 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

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


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)