How to replicate the changes from INotifyCollectionChanged on another collection

91 views Asked by At

I have 2 ObservableCollection<T> objects. Let's call them A and B. I want to replicate the changes broadcasted by A (via INotifyCollectionChanged) to B.

In other words, when A changes, B must replicate the same change:

  • When A added an item, B must add the same item.
  • When A moved an item, B must do the same move.
  • And so on...

The problem comes with the complexity of NotifyCollectionChangedEventArgs. I'd like to avoid writing the code that checks for all operation combinations.

(add + remove + reset + move + replace) x (single-item + multi-item) - (invalid combinations)

My assumption (and hope) is that this logic already exists in .Net (I'm on .Net 6).

Here's some code that demonstrates my vision.

ObservableCollection<int> A = new ObservableCollection<int>();
ObservableCollection<int> B = new ObservableCollection<int>();

A.CollectionChanged += (s, args) =>
{
    // This line doesn't build. It's just to show my intent.
    B.Apply(args);
};

A.Add(1);
A.Add(2);

// At this point B should contain 1 and 2 because they were added to A.

Is there an existing .Net solution for this problem?

If the recipe doesn't exist, any pointers on how to properly implement it are appreciated.

1

There are 1 answers

1
Aaron Ford On

I'm not entirely sure what you are trying to achieve - but if A and B are always going to be equivalent, why not just find an abstraction that allows the use of A and remove the B collection? But if B is going to be modified independently of A then the operations - such as move - won't work in B given the difference in collections and indices.

If there is no possibility of removing one instance of the collection then you could always write a class that makes the code of your handler simpler.

var A = new ObservableCollection<int>();
var B = new ObservableCollection<int>();

var evts = new ObservableCollectionEvents<int>(A);
evts.Added += (i, x) => B.Insert(i, x);
evts.Removed += (i, x) => B.RemoveAt(i);

A.Add(1);
A.Add(2);

Console.WriteLine(string.Join(", ", B));

class ObservableCollectionEvents<T>
{
    public event Action<int, T>? Added;
    public event Action<int, T>? Removed;

    public ObservableCollectionEvents(ObservableCollection<T> collection)
    {
        collection.CollectionChanged += OnCollectionChanged;
    }

    private void OnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
    {
        switch (e.Action)
        {
            case NotifyCollectionChangedAction.Add:
                for (var i = e.NewStartingIndex; i < e.NewStartingIndex + e.NewItems!.Count; ++i)
                    Added?.Invoke(i, (T)e.NewItems[i - e.NewStartingIndex]!);
                break;
            case NotifyCollectionChangedAction.Remove:
                for (var i = e.OldStartingIndex; i < e.OldStartingIndex + e.OldItems!.Count; ++i)
                    Removed?.Invoke(i, (T)e.OldItems[i - e.OldStartingIndex]!);
                break;
            // ...
        }
    }
}

This would also allow you to simplify the amount of operations you need to support. A replace could be modelled with a remove followed by an add at the same index - with a similar process for the move operations.