stride3d/stride · error · ArgumentException

The list must implements INotifyCollectionChanged

Error message

The list must implements INotifyCollectionChanged

What it means

NonGenericObservableCollectionWrapper's constructor requires the wrapped IList<T> to implement both INotifyPropertyChanged and INotifyCollectionChanged, because it forwards those events to WPF bindings. If the list lacks INotifyCollectionChanged, it throws this ArgumentException immediately so silent binding breakage is avoided. Pass an ObservableList<T> (which implements both) or another fully observable collection.

Solutions

  1. Pass a Stride ObservableList<T> (or ObservableCollection<T>) as the wrapped list; it implements both required interfaces.
  2. If the source must stay a plain List<T>, replace it with ObservableList<T> and copy elements, or wrap changes manually.
  3. If you own the custom collection, implement INotifyCollectionChanged (raise CollectionChanged on Add/Remove/Clear/Replace).

Example fix

// before
var wrapper = new MyWrapper(new List<Item>(items));
// after
var list = new ObservableList<Item>(items);
var wrapper = new MyWrapper(list);
Defensive patterns

Strategy: validation

Validate before calling

if (list == null) throw new ArgumentNullException(nameof(list));
if (!(list is INotifyPropertyChanged)) throw new ArgumentException("List must implement INotifyPropertyChanged", nameof(list));
if (!(list is INotifyCollectionChanged)) throw new ArgumentException("List must implement INotifyCollectionChanged", nameof(list));

Type guard

bool IsWrapperCompatible<T>(IList<T> list) => list is INotifyPropertyChanged && list is INotifyCollectionChanged;

Try / catch

try { var wrapper = new MyWrapper(list); }
catch (ArgumentException ex) { log.Error("Wrapped list is not fully observable", ex); list = new ObservableList<T>(list); wrapper = new MyWrapper(list); }

Prevention

When it happens

Trigger: Calling the protected NonGenericObservableCollectionWrapper(IList<T> list) constructor (from a derived wrapper class) with a List<T>, Collection<T>, array, or any IList<T> that implements INotifyPropertyChanged but not INotifyCollectionChanged.

Common situations: Subclassing the wrapper to expose a plain List<T> or a custom collection to WPF; swapping the backing collection type during a refactor from ObservableList<T> to a standard collection; third-party collection types that support change notification partially.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of stride3d/stride@96fad776d2 (2026-09-14). Data as JSON: /api/errors/9f9abacf8e62d798. Report an issue: GitHub.

Appendix: source

Thrown at sources/presentation/Stride.Core.Presentation.Wpf/Collections/NonGenericObservableCollectionWrapper.cs:34

    /// <remarks>
    /// In some scenarii, <see cref="IList"/> does not support range changes on the collection (Especially when bound to a ListCollectionView).
    /// This is why the <see cref="ObservableList{T}"/> and the <see cref="ObservableSet{T}"/> class does not implement this interface directly.
    /// However this wrapper class can be used when the <see cref="IList"/> interface is required.
    /// </remarks>
    /// <typeparam name="T">The type of item contained in the <see cref="ObservableList{T}"/>.</typeparam>
    public abstract class NonGenericObservableCollectionWrapper<T> : IList, IList<T>, INotifyPropertyChanged, INotifyCollectionChanged
    {
        [NotNull] protected readonly IList<T> List;

        /// <summary>
        /// Initializes a new instance of the <see cref="NonGenericObservableListWrapper{T}"/> class.
        /// </summary>
        /// <param name="list">The <see cref="ObservableList{T}"/> to wrap.</param>
        protected NonGenericObservableCollectionWrapper([NotNull] IList<T> list)
        {
            if (list == null) throw new ArgumentNullException(nameof(list));
            if (!(list is INotifyPropertyChanged)) throw new ArgumentException(@"The list must implements INotifyPropertyChanged", nameof(list));
            if (!(list is INotifyCollectionChanged)) throw new ArgumentException(@"The list must implements INotifyCollectionChanged", nameof(list));

            List = list;
            ((INotifyPropertyChanged)List).PropertyChanged += (sender, e) => PropertyChanged?.Invoke(this, e);
            ((INotifyCollectionChanged)List).CollectionChanged += (sender, e) => CollectionChanged?.Invoke(this, e);
        }

        /// <inheritdoc/>
        public object this[int index] { get { return List[index]; } set { List[index] = (T)value; } }

        /// <inheritdoc/>
        T IList<T>.this[int index] { get { return List[index]; } set { List[index] = value; } }

        /// <inheritdoc/>
        public bool IsReadOnly => List.IsReadOnly;

        /// <inheritdoc/>
        public bool IsFixedSize => false;

View on GitHub (pinned to 96fad776d2)