title: How to build a modern, synchronized ObservableCollection?
body: <p>After reading <a href="https://thomaslevesque.com/2009/04/17/wpf-binding-to-an-asynchronous-collection/" rel="nofollow noreferrer">several</a> <a href="http://10rem.net/blog/2012/01/20/wpf-45-cross-thread-collection-synchronization-redux" rel="nofollow noreferrer">tutorials</a>, <a href="https://www.codeproject.com/tips/414407/thread-safe-improvement-for-observablecollection" rel="nofollow noreferrer">snippets</a> (seems either source or almost 1:1 copy of @Xcalibur37's <a href="https://xcalibur37.wordpress.com/2014/02/13/making-a-better-observablecollection-part-2-cross-threading/" rel="nofollow noreferrer">blog post</a>) &amp; of course their &quot;originating&quot; <a href="https://stackoverflow.com/questions/21720638/using-bindingoperations-enablecollectionsynchronization">questions</a> <a href="https://stackoverflow.com/questions/20634057/manualresetevent-waitone-blocks-the-owner-thread-of-my-collectionview/20730354#20730354">on</a> <a href="https://stackoverflow.com/questions/32403139/c-sharp-how-to-code-enablecollectionsynchronization-for-collection-of-collection">SO</a>, I'm not only still confused about cross-thread access, but also struggling to get my WPF application to correctly perform binding updates upon <code>CollectionChanged</code> - which works upon launch &amp; delete, but not for inserting copies.</p>
  <p>SO is all about code, so let's get straight to it - both collections first, then VM, &quot;works&quot; &amp; &quot;fails&quot;:</p>
  <h3>Synchronized <code>ObservableCollection&lt;T&gt;</code> class:</h3>
  <pre class="lang-csharp prettyprint-override"><code>public class SynchronizedCollection&lt;T&gt; : ObservableCollection&lt;T&gt; where T : class
  {
    // AFAICT, event overriding is needed, yet my app behaves the same without it?!
    public override event NotifyCollectionChangedEventHandler CollectionChanged;
  
    public SynchronizedCollection()
    {
      // Implemented this in my base-ViewModel's ctor first, but
      // a) read somewhere that it's supposed to be done here instead
      // b) call in base-VM resulted in 1 invocation per collection, though for _all_ VM at once!
      BindingOperations.CollectionRegistering += (sender, eventArgs) =&gt;
      {
        if (eventArgs.Collection.Equals(this)) // R# suggested, Equals() is wiser than == here.
        {
          BindingOperations.EnableCollectionSynchronization(this, SynchronizationLock);
        }
      };
    }
  
    // Can't be static due to class type parameter, but readonly should do.
    // Also, since EnableCollectionSynchronization() is called in ctor, 1 lock object per collection.
    private object SynchronizationLock { get; } = new object();
  
    protected override void InsertItem(int index, T item)
    {
      lock (SynchronizationLock)
      {
        base.InsertItem(index, item); 
      }
    }
  
  
    // Named InsertItems instead of AddRange for consistency.
    public void InsertItems(IEnumerable&lt;T&gt; items)
    {
      var list = items as IList&lt;T&gt; ?? items.ToList();
      int start = Count;
      foreach (T item in list)
      {
        lock (SynchronizationLock)
        {
          Items.Add(item); 
        }
      }
  
      // Multi-insert, but notify only once after completion.
      OnPropertyChanged(new PropertyChangedEventArgs(nameof(Count)));
      OnPropertyChanged(new PropertyChangedEventArgs(&quot;Item[]&quot;));
      OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, list, start));
    }
  
    // Code left out for brevity...
  
    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs eventArgs)
    {
      lock (SynchronizationLock)
      {
        if (!(CollectionChanged is NotifyCollectionChangedEventHandler eventHandler))
        {
          return;
        }
  
        foreach (Delegate @delegate in eventHandler.GetInvocationList())
        {
          var handler = (NotifyCollectionChangedEventHandler)@delegate;
          if (handler.Target is DispatcherObject current &amp;&amp; !current.CheckAccess())
          {
            current.Dispatcher.Invoke(DispatcherPriority.DataBind, handler, this, eventArgs);
          }
          else
          {
            handler(this, eventArgs);
          }
        }
      }
    }
  }
  </code></pre>
  <h3><code>INotifyPropertyChanged</code> support for above's <code>SynchronizedCollection</code> items:</h3>
  <pre class="lang-csharp prettyprint-override"><code>public class NotifySynchronizedCollection&lt;T&gt; : SynchronizedCollection&lt;T&gt;, INotifySynchronizedCollection
    where T : class
  {
    public event CollectionItemPropertyChangedEventHandler CollectionItemPropertyChanged;
  
    // Code left out for brevity...
  
    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs eventArgs)
    {
      // Seems to me like lock() isn't needed here...
      //lock (SynchronizationLock)
      //{
        switch (eventArgs.Action)
        {
          case NotifyCollectionChangedAction.Add:
            RegisterItemPropertyChanged(eventArgs.NewItems);
            break;
  
          case NotifyCollectionChangedAction.Remove:
          case NotifyCollectionChangedAction.Reset when !(eventArgs.OldItems is null):
            UnregisterItemPropertyChanged(eventArgs.OldItems);
            break;
  
          case NotifyCollectionChangedAction.Move:
          case NotifyCollectionChangedAction.Replace:
            UnregisterItemPropertyChanged(eventArgs.OldItems);
            RegisterItemPropertyChanged(eventArgs.NewItems);
            break;
        }
      //}
    }
  
    private void OnItemPropertyChanged(object item, PropertyChangedEventArgs itemArgs) =&gt;
      CollectionItemPropertyChanged?.Invoke(this, item, itemArgs);
  
    private void RegisterItemPropertyChanged(IEnumerable items)
    {
      foreach (INotifyPropertyChanged item in items)
      {
        if (item != null)
        {
          item.PropertyChanged += OnItemPropertyChanged;
        }
      }
    }
  
    private void UnregisterItemPropertyChanged(IEnumerable items)
    {
      foreach (INotifyPropertyChanged item in items)
      {
        if (item != null)
        {
          item.PropertyChanged -= OnItemPropertyChanged;
        }
      }
    }
  }
  </code></pre>
  <h3>One of many ViewModels (uses <a href="https://github.com/brminnick/AsyncAwaitBestPractices/" rel="nofollow noreferrer">AsyncAwaitBestPractices.MVVM</a>'s <code>IAsyncCommand</code>):</h3>
  <pre class="lang-csharp prettyprint-override"><code>public class OrdersViewModel : BaseViewModel
  {
    // BindingOperations.EnableCollectionSynchronization was once in BaseViewModel's ctor (with
    // mentioned side-effects at this question's intro) &amp; even right in this VM's ctor - none of
    // the tutorials I've found mentioned a solution for tedious EnableCollectionSynchronization
    // calls for each collection, in each VM, hence I tried CollectionRegistering in base-VM...
  
    // Code left out for brevity...
  
    public OrdersViewModel(INavigationService navService, IOrderDataService dataService)
      : base(navService)
    {
      DataService = dataService;
      RegisterMessages();
    }
  
    // Code left out for brevity...
  
    // Note: This works, except for the view which doesn't show the newly added item!
    //       However, another TextBlock-binding for Orders.Count _does_ update?!
    //       Using ConfigureAwait(true) inside instead didn't help either...
    public IAsyncCommand&lt;OrderModel&gt; CopyCommand =&gt;
      _copy ?? (_copy = new AsyncRelayCommand&lt;OrderModel&gt;(
        async original =&gt;
        {
          if (!await ShowConfirmation(&quot;Copy this order?&quot;).ConfigureAwait(false))
          {
            return;
          }
  
          if (original.ProductId &lt; 1)
          {
            throw new ArgumentOutOfRangeException(
              nameof(original.ProductId),
              original.ProductId,
              @&quot;Valid product missing.&quot;);
          }
  
          await AddOrder(
            await DataService.CreateOrderCopy(original.Id).ConfigureAwait(false)
              ?? throw new ArgumentNullException(nameof(original.Id), $@&quot;Copying failed.&quot;))
            .ConfigureAwait(false);
        },
        original =&gt; original.Id &gt; 0,
        async exception =&gt; await ShowError(&quot;Copying&quot;, exception).ConfigureAwait(false)));
  
    // Note: This works!
    public IAsyncCommand&lt;OrderModel&gt; Delete =&gt;
      _delete ?? (_delete = new AsyncCommand&lt;OrderModel&gt;(
        async deletable =&gt;
        {
          bool isChild = deletable.ParentId &gt; 0;
          if (!await ShowConfirmation($&quot;Delete this order?&quot;).ConfigureAwait(false))
          {
            return;
          }
  
          await DataService.DeleteOrder(deletable.Id).ConfigureAwait(false);
          if (isChild)
          {
            await RefreshParent(Orders.Single(order =&gt; order.Id == deletable.ParentId))
              .ConfigureAwait(false);
          }
  
          Orders.Remove(deletable);
          await ShowInfo($&quot;Order deleted.&quot;).ConfigureAwait(false);
        },
        deletable =&gt; (deletable.ParentId &gt; 0)
                     || (Orders.SingleOrDefault(order =&gt; order.Id == deletable.Id)
                        ?.ChildrenCount &lt; 1),
        async exception =&gt; await ShowError(&quot;Deletion&quot;, exception).ConfigureAwait(false)));
  
    private async Task AddOrder(int orderId)
    {
      // Note: Using ConfigureAwait(true) doesn't help either.
      //       But while 
      Orders.Add(await GetOrder(orderId, false).ConfigureAwait(false));
    }
  
    // Code left out for brevity...
  
    private void RegisterMessages()
    {
      Default.Register&lt;OrdersInitializeMessage&gt;(this, async message =&gt;
      {
        Orders.Clear();
        Task&lt;CustomerModel&gt; customerTask = DataService.GetCustomer(message.CustomerId);
        Task&lt;List&lt;OrderModel&gt;&gt; ordersTask = DataService.GetOrders(message.OrderId);
        await Task.WhenAll(customerTask, ordersTask).ConfigureAwait(false);
  
        Customer = await customerTask.ConfigureAwait(false) ?? Customer;
        (await ordersTask.ConfigureAwait(false)).ForEach(Orders.Add);  // NOTE: This works!
        SelectedOrder =
          Orders.Count == 1
            ? Orders[0]
            : Orders.SingleOrDefault(order =&gt; order.Id == message.OrderId);
      });
  
      // Code left out for brevity...
    }
  }
  </code></pre>
  <p>Why do both the <code>Delete</code> command and <code>Orders.Add()</code> (inside <code>RegisterMessages()</code>) work, while the <code>Copy</code> command's <code>Orders.Add()</code> call doesn't?</p>
  <p>The <code>Delete</code> command uses <code>Orders.Remove(deletable);</code> which in turn calls my overridden <code>RemoveItem</code> in <code>SynchronizedCollection&lt;T&gt;</code> which is implemented just like above's <code>InsertItem</code>)</p>