Search code examples
c#containersobservablecollectioninotifypropertychangedpostsharp

When to use ObservableCollection vs AdvisableCollection w/ PostSharp NotifyPropertyChanged?


Here is the PostSharp support discussion (titled: "NotifyPropertyChanged and ObservableCollection") that sparked my initial confusion on this topic. Further explanation is in the answer I posted (not 100% confident in my answer).

I'm a little confused on how PostSharp's [NotifyPropertyChanged] aspect affects a class's collection properties (if at all), and whether I need to be using an ObservableCollection<> or AdvisableCollection<>. If I understand correctly, if I want notifications from the collection, I MUST change it to at least an ObservableCollection<>, correct? The [NotifyPropertyChanged] aspect doesn't magically make whatever collection types are in the class observable.

If that's the case, then when would I use ObservableCollection<> or AdvisableCollection<>? Should AdvisableCollection<> be reserved for when I need the aggregation pattern applied by PostSharp? Or should I just always use AdvisableCollection<> whenever applying PostSharp's [NotifyPropertyChanged] attribute?

[NotifyPropertyChanged]
public class Test {
    public int PropVal { get; set; }
    public List<string> PropCollection { get; set; } //Should this be ObservableCollection
                                                    //or AdvisableCollection?
}

Solution

  • Congratulations on the Tumbleweed badge and many apologies for the delayed answer.

    Collections are currently not well integrated with the aspect in sense that there is a bit of work needed for real-life examples to work properly. Let me explain few key steps:

    [AggregateAllChanges] instructs the runtime that it should relay any change observed on the collection as a change of the property itself. This in particular means that a virtual Item[] dependency is created (which is used by a "standard" collections to notify change of collection shallow "state").

    In reality properties working with collections content are usually aggregates in some sense, depending not only on set of items stored in the collection but also on state of individual objects in the collection. Currently there is no way to express that and/or selectively relay these changes. For this you need to create a class derived from ObservableCollection<T> that looks like following:

    [NotifyPropertyChanged]
    public class ObservableCollectionEx<T> : ObservableCollection<T>
    {
        protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            if (e.Action == NotifyCollectionChangedAction.Remove)
            {
                foreach (T item in e.OldItems)
                {
                    ((INotifyPropertyChanged)item).PropertyChanged -= OnItemPropertyChanged;
                }
            }
            else if (e.Action == NotifyCollectionChangedAction.Add)
            {
                foreach (T item in e.NewItems)
                {
                    ((INotifyPropertyChanged)item).PropertyChanged += OnItemPropertyChanged;
                }
            }
    
            base.OnCollectionChanged(e);
        }
    
        protected void OnPropertyChanged(string propertyName)
        {
            base.OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
        }
    
        protected void OnItemPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            NotifyPropertyChangedServices.SignalPropertyChanged(this, "Item[]");
    
            NotifyCollectionChangedEventArgs collectionChangedEventArgs = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
            base.OnCollectionChanged(collectionChangedEventArgs);
        }
    }
    

    The above does exactly what is needed - when anything changes on any object within the collection, collection reports that it itself had changed.

    Now in a class it would look like this:

    [NotifyPropertyChanged]
    public class TestClass
    {
        [AggregateAllChanges]
        public ObservableCollectionEx<TestItem> Items { get; } = new ObservableCollectionEx<TestItem>();
    
        [SafeForDependencyAnalysis]
        public int Sum
        {
            get
            {
                if (Depends.Guard)
                {
                    Depends.On(this.Items);
                }
    
                return this.Items.Sum(x => x.Value);
            }
        }
    }
    
    [NotifyPropertyChanged]
    public class TestItem
    {
        public int Value { get; set; }
    }
    

    The above is not ideal performance-wise if there are other properties on the TestItem class that are frequently changed. In that case, one may consider to add a some kind of filtering to ObservableCollectionEx.

    Regarding AdvisableCollection<T> you are right - while it can be used, it's main purpose is different.