meziantou/Meziantou.Framework

ConcurrentObservableCollection -> Remove Item

Closed this issue · 3 comments

Hi! I´m having an issue using the ConcurrentObservableCollection, C#, WPF. Simple Setup:

In XAML I´m using a ComboBox within a UserControl. The ComboBox is bound to a ConcurrentObservableCollection using AsObservable and is also bound to the SelectedItem.

When trying to remove the SelectedItem from within the ViewModel like this:

if (TransferLists.Contains(SelectedTransferList)) TransferLists.Remove(SelectedTransferList); //TransferLists.Contains(SelectedTransferList)) returns "true"

I get the following Error (NullReferenceException):

bei Meziantou.Framework.WPF.Collections.ConcurrentObservableCollection1.System.Collections.IList.Contains(Object value) in /_/src/Meziantou.Framework.WPF/Collections/ConcurrentObservableCollection.cs: Zeile275 bei Meziantou.Framework.WPF.Collections.DispatchedObservableCollection1.System.Collections.IList.Contains(Object value) in /_/src/Meziantou.Framework.WPF/Collections/Internals/DispatchedObservableCollection.cs: Zeile345
bei System.Windows.Data.ListCollectionView.InternalContains(Object item)
bei System.Windows.Data.ListCollectionView.Contains(Object item)
bei System.Windows.Controls.ItemCollection.Contains(Object containItem)
bei System.Windows.Controls.Primitives.Selector.SelectionChanger.Select(ItemInfo info, Boolean assumeInItemsCollection)
bei System.Windows.Controls.Primitives.Selector.SelectionChanger.SelectJustThisItem(ItemInfo info, Boolean assumeInItemsCollection)
bei System.Windows.Controls.Primitives.Selector.OnSelectedItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
bei System.Windows.DependencyObject.OnPropertyChanged(DependencyPropertyChangedEventArgs e)
bei System.Windows.FrameworkElement.OnPropertyChanged(DependencyPropertyChangedEventArgs e)
bei System.Windows.DependencyObject.NotifyPropertyChange(DependencyPropertyChangedEventArgs args)
bei System.Windows.DependencyObject.UpdateEffectiveValue(EntryIndex entryIndex, DependencyProperty dp, PropertyMetadata metadata, EffectiveValueEntry oldEntry, EffectiveValueEntry& newEntry, Boolean coerceWithDeferredReference, Boolean coerceWithCurrentValue, OperationType operationType)
bei System.Windows.DependencyObject.InvalidateProperty(DependencyProperty dp, Boolean preserveCurrentValue)
bei System.Windows.Data.BindingExpressionBase.Invalidate(Boolean isASubPropertyChange)
bei System.Windows.Data.BindingExpression.TransferValue(Object newValue, Boolean isASubPropertyChange)
bei System.Windows.Data.BindingExpression.ScheduleTransfer(Boolean isASubPropertyChange)
bei MS.Internal.Data.ClrBindingWorker.NewValueAvailable(Boolean dependencySourcesChanged, Boolean initialValue, Boolean isASubPropertyChange)
bei MS.Internal.Data.PropertyPathWorker.UpdateSourceValueState(Int32 k, ICollectionView collectionView, Object newValue, Boolean isASubPropertyChange)
bei MS.Internal.Data.ClrBindingWorker.OnSourcePropertyChanged(Object o, String propName)
bei MS.Internal.Data.PropertyPathWorker.OnPropertyChanged(Object sender, PropertyChangedEventArgs e)
bei System.Windows.WeakEventManager.ListenerList`1.DeliverEvent(Object sender, EventArgs e, Type managerType)
bei System.ComponentModel.PropertyChangedEventManager.OnPropertyChanged(Object sender, PropertyChangedEventArgs args)
bei AGT.Main.ViewModel.BaseViewModel.OnPropertyChanged(String propertyName) in C:\CODE_COPY\Agt\Solace_MA\AGT.Main\ViewModel\BaseViewModel.cs: Zeile19

The Error is thrown in my BaseViewModel in the OnPropertyChanged Method:

` public class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;

    public virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    public virtual bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = "")
    {
        if (EqualityComparer<T>.Default.Equals(storage, value)) return false;
        storage = value;
        OnPropertyChanged(propertyName);
        CommandManager.InvalidateRequerySuggested();
        return true;
    }
}`

Do you have an idea how to overcome this error or what I´m doing wrong?

Thanks a lot in advance!

Chris

The error is not triggered by the remove method itself. When removing the item, also the setter of SelectedTransferList property is triggered by wpf user control causing a call of the SetProperty method in the BaseViewModel. This is when the Exception is eventually thrown. If I remove an item that is not selected, therefore not beeing displayed and not triggering SetProperty, all is fine.

Is it maybe some kind of race condition, because the item is not removed at once in ConcurrentObservableCollection, but is enqueued? Same code with a normal ObservableCollection runs without an issue. All methods used for this example are synchronuous.

If you look at the Contains method, the only part of the method that can throw a NullReferenceException in some specific cases is the explicit cast:

        // https://github.com/meziantou/Meziantou.Framework/blob/1d2c6f6b8f96d161f4675fe55ae26427a5c651d6/src/Meziantou.Framework.WPF/Collections/ConcurrentObservableCollection.cs#L275
        bool IList.Contains(object? value)
        {
            AssertType(value, nameof(value));
            return Contains((T)value!); // The cast here (line 275)
        }
  • What are the types of TransferLists and SelectedTransferList?
  • Do you have a small repro, or at least some lines of code to explain the usage?
  • Which version of .NET do you use? (net48, netcoreapp3.1, net5, ...)