{"record":{"id":"001e4e8a14d2cd3a","repo":"stride3d/stride","slug":"the-list-must-implements-inotifypropertychanged","errorCode":null,"errorMessage":"The list must implements INotifyPropertyChanged","messagePattern":"The list must implements INotifyPropertyChanged","errorType":"exception","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"sources/presentation/Stride.Core.Presentation.Wpf/Collections/NonGenericObservableCollectionWrapper.cs","lineNumber":33,"sourceCode":"    /// </summary>\n    /// <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;","sourceCodeStart":15,"sourceCodeEnd":51,"githubUrl":"https://github.com/stride3d/stride/blob/96fad776d210c221682aac1ccdf4c79dc046fc38/sources/presentation/Stride.Core.Presentation.Wpf/Collections/NonGenericObservableCollectionWrapper.cs#L15-L51","documentation":"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.","triggerScenarios":"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>()).","commonSituations":"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.","solutions":["Pass an ObservableCollection<T> or Stride's ObservableList<T>, both of which implement INotifyPropertyChanged and INotifyCollectionChanged.","If the source must remain a plain List<T>, wrap the list in an ObservableCollection<T> first, then wrap that in the wrapper.","Implement INotifyPropertyChanged and INotifyCollectionChanged on a custom collection class before wrapping it."],"exampleFix":"// before\nvar wrapper = new NonGenericObservableCollectionWrapper<string>(new List<string> { \"a\", \"b\" });\n\n// after\nvar wrapper = new NonGenericObservableCollectionWrapper<string>(\n    new ObservableCollection<string> { \"a\", \"b\" });","handlingStrategy":"validation","validationCode":"if (!(list is INotifyPropertyChanged) || !(list is INotifyCollectionChanged))\n    throw new ArgumentException(\"Wrapped list must implement INotifyPropertyChanged and INotifyCollectionChanged.\");","typeGuard":"static bool IsObservableList<T>(IList<T> list) =>\n    list is INotifyPropertyChanged && list is INotifyCollectionChanged;","tryCatchPattern":"try { var wrapper = new NonGenericObservableCollectionWrapper<string>(list); }\ncatch (ArgumentException ex) when (ex.Message.Contains(\"INotifyPropertyChanged\")) { log.Error(\"Wrap an ObservableCollection<T>/ObservableList<T> instead of a plain list\"); }","preventionTips":["Only pass ObservableCollection<T> or Stride ObservableList<T> into the wrapper.","Never wrap raw List<T>, arrays, or legacy collections expecting UI change notifications.","Add a static helper that converts a plain IList<T> into ObservableCollection<T> before wrapping."],"tags":["wpf","collections","inotifypropertychanged","argument"],"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-23T08:17:48.524Z"}