stride3d/stride · error · ArgumentException

The list must implements INotifyPropertyChanged

Error message

The list must implements INotifyPropertyChanged

What it means

NonGenericObservableCollectionWrapper wraps an IList<T> and re-exposes its change notifications. Because it forwards INotifyPropertyChanged.PropertyChanged and INotifyCollectionChanged.CollectionChanged events, the wrapped list must implement both interfaces; the constructor validates this and throws ArgumentException if the list does not implement INotifyPropertyChanged.

Solutions

  1. Pass an ObservableCollection<T> or Stride's ObservableList<T>, both of which implement INotifyPropertyChanged and INotifyCollectionChanged.
  2. If the source must remain a plain List<T>, wrap the list in an ObservableCollection<T> first, then wrap that in the wrapper.
  3. Implement INotifyPropertyChanged and INotifyCollectionChanged on a custom collection class before wrapping it.

Example fix

// before
var wrapper = new NonGenericObservableCollectionWrapper<string>(new List<string> { "a", "b" });

// after
var wrapper = new NonGenericObservableCollectionWrapper<string>(
    new ObservableCollection<string> { "a", "b" });
Defensive patterns

Strategy: validation

Validate before calling

if (!(list is INotifyPropertyChanged) || !(list is INotifyCollectionChanged))
    throw new ArgumentException("Wrapped list must implement INotifyPropertyChanged and INotifyCollectionChanged.");

Type guard

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

Try / catch

try { var wrapper = new NonGenericObservableCollectionWrapper<string>(list); }
catch (ArgumentException ex) when (ex.Message.Contains("INotifyPropertyChanged")) { log.Error("Wrap an ObservableCollection<T>/ObservableList<T> instead of a plain list"); }

Prevention

When it happens

Trigger: Calling the wrapper's constructor with a plain List<T>, T[], Collection<T>, ObservableCollection without INPC, or any custom IList<T> that doesn't raise change notifications — e.g. new NonGenericObservableCollectionWrapper<string>(new List<string>()).

Common situations: Wrapping an in-memory List<T> to bind it to UI, expecting automatic updates; passing a legacy collection type that predates INotifyCollectionChanged; third-party collection APIs returning plain IList<T> implementations.

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/001e4e8a14d2cd3a. Report an issue: GitHub.

Appendix: source

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

    /// </summary>
    /// <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)