{"record":{"id":"9f9abacf8e62d798","repo":"stride3d/stride","slug":"the-list-must-implements-inotifycollectionchanged","errorCode":null,"errorMessage":"The list must implements INotifyCollectionChanged","messagePattern":"The list must implements INotifyCollectionChanged","errorType":"exception","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"sources/presentation/Stride.Core.Presentation.Wpf/Collections/NonGenericObservableCollectionWrapper.cs","lineNumber":34,"sourceCode":"    /// <remarks>\n    /// In some scenarii, <see cref=\"IList\"/> does not support range changes on the collection (Especially when bound to a ListCollectionView).\n    /// This is why the <see cref=\"ObservableList{T}\"/> and the <see cref=\"ObservableSet{T}\"/> class does not implement this interface directly.\n    /// However this wrapper class can be used when the <see cref=\"IList\"/> interface is required.\n    /// </remarks>\n    /// <typeparam name=\"T\">The type of item contained in the <see cref=\"ObservableList{T}\"/>.</typeparam>\n    public abstract class NonGenericObservableCollectionWrapper<T> : IList, IList<T>, INotifyPropertyChanged, INotifyCollectionChanged\n    {\n        [NotNull] protected readonly IList<T> List;\n\n        /// <summary>\n        /// Initializes a new instance of the <see cref=\"NonGenericObservableListWrapper{T}\"/> class.\n        /// </summary>\n        /// <param name=\"list\">The <see cref=\"ObservableList{T}\"/> to wrap.</param>\n        protected NonGenericObservableCollectionWrapper([NotNull] IList<T> list)\n        {\n            if (list == null) throw new ArgumentNullException(nameof(list));\n            if (!(list is INotifyPropertyChanged)) throw new ArgumentException(@\"The list must implements INotifyPropertyChanged\", nameof(list));\n            if (!(list is INotifyCollectionChanged)) throw new ArgumentException(@\"The list must implements INotifyCollectionChanged\", nameof(list));\n\n            List = list;\n            ((INotifyPropertyChanged)List).PropertyChanged += (sender, e) => PropertyChanged?.Invoke(this, e);\n            ((INotifyCollectionChanged)List).CollectionChanged += (sender, e) => CollectionChanged?.Invoke(this, e);\n        }\n\n        /// <inheritdoc/>\n        public object this[int index] { get { return List[index]; } set { List[index] = (T)value; } }\n\n        /// <inheritdoc/>\n        T IList<T>.this[int index] { get { return List[index]; } set { List[index] = value; } }\n\n        /// <inheritdoc/>\n        public bool IsReadOnly => List.IsReadOnly;\n\n        /// <inheritdoc/>\n        public bool IsFixedSize => false;\n","sourceCodeStart":16,"sourceCodeEnd":52,"githubUrl":"https://github.com/stride3d/stride/blob/96fad776d210c221682aac1ccdf4c79dc046fc38/sources/presentation/Stride.Core.Presentation.Wpf/Collections/NonGenericObservableCollectionWrapper.cs#L16-L52","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Pass a Stride ObservableList<T> (or ObservableCollection<T>) as the wrapped list; it implements both required interfaces.","If the source must stay a plain List<T>, replace it with ObservableList<T> and copy elements, or wrap changes manually.","If you own the custom collection, implement INotifyCollectionChanged (raise CollectionChanged on Add/Remove/Clear/Replace)."],"exampleFix":"// before\nvar wrapper = new MyWrapper(new List<Item>(items));\n// after\nvar list = new ObservableList<Item>(items);\nvar wrapper = new MyWrapper(list);","handlingStrategy":"validation","validationCode":"if (list == null) throw new ArgumentNullException(nameof(list));\nif (!(list is INotifyPropertyChanged)) throw new ArgumentException(\"List must implement INotifyPropertyChanged\", nameof(list));\nif (!(list is INotifyCollectionChanged)) throw new ArgumentException(\"List must implement INotifyCollectionChanged\", nameof(list));","typeGuard":"bool IsWrapperCompatible<T>(IList<T> list) => list is INotifyPropertyChanged && list is INotifyCollectionChanged;","tryCatchPattern":"try { var wrapper = new MyWrapper(list); }\ncatch (ArgumentException ex) { log.Error(\"Wrapped list is not fully observable\", ex); list = new ObservableList<T>(list); wrapper = new MyWrapper(list); }","preventionTips":["Always wrap ObservableList<T> or ObservableCollection<T>, never List<T>.","Add an interface assertion/unit test for any custom collection passed to the wrapper.","Keep the backing collection type fixed when refactoring wrapper consumers."],"tags":["wpf","argument-validation","observable-collection"],"backgroundTag":"invalid-argument-value","analyzedSha":"96fad776d210c221682aac1ccdf4c79dc046fc38","analyzedAt":"2026-09-14T02:59:31.279Z","contentChangedAt":"2026-09-14T02:59:31.279Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}