1

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.

Batesias
  • 1,914
  • 1
  • 12
  • 22

1 Answers1

1

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.

Aaron Ford
  • 11
  • 1
  • There are various situation where this recipe can be useful: Imagine that A comes from an external library and can't be modified and isn't UI-Thread-friendly, B can be introduced as a UI-Thread-friendly collection that can be used in XAML data bindings. – Batesias Feb 14 '23 at 16:15