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
- 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.
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
- 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.
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
- Container must be of type FrameworkElement
- index
- Invalid offset ( ) or count + ( ) for source length
- Multiple items in the collection are changing concurrently.
- points and texts must have the same number of elements.
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)