2

Heres a quick question. I have an ObservableCollection<IItem> where IItem has a property called Id. Throughout the lifetime of an application items are added, removed and then re-added once again to this collection. What I need is to track when items with certain id's are present in this collection. When all required dependencies are present, I need to do some initialization, if at least one of the required items is removed, then I need to do a cleanup. If that item is then re-added once again, then I need to do initialization again. Any suggestions what RX operators to use to build such kind of a query?

L.E.O
  • 1,029
  • 1
  • 12
  • 27

2 Answers2

1

Keeping track of the state of the collection will probably be somewhat tedious. Unless your collection is very big you can instead examine the collection on each change to determine if your criteria for initialization is fulfilled. Then you can use DistinctUntilChanged to get an observable that will fire when you need to perform initialization and cleanup

Here is an example:

var collection = new ObservableCollection<Int32>();
var observable = Observable
  .FromEventPattern<NotifyCollectionChangedEventHandler, NotifyCollectionChangedEventArgs>(
    handler => collection.CollectionChanged += handler,
    handler => collection.CollectionChanged -= handler
  );

You then need a predicate that determines if initialization is required (the collection "is ready"). This predicate can get expensive if your collection is big because it will be called on each change to the collection, but my assumption is that this is not a problem.

Boolean IsReady(IEnumerable<Int32> items, IReadOnlyList<Int32> itemsRequiredToBeReady) {
  return items.Intersect(itemsRequiredToBeReady).Count() == itemsRequiredToBeReady.Count;
}

Then you can use DistinctUntilChanged to get notifications when the IsReady predicate changes from true to false and vice versa:

var isReadyObservable = observable
  .Select(ep => IsReady((ObservableCollection<Int32>) ep.Sender, ItemsRequiredToBeReady))
  .DistinctUntilChanged();

To initialize and cleanup you need two subscriptions:

isReadyObservable.Where(isReady => isReady).Subscribe(_ => Initialize());
isReadyObservable.Where(isReady => !isReady).Subscribe(_ => Cleanup());
Martin Liversage
  • 96,855
  • 20
  • 193
  • 238
0

ObservableCollection is not quite observable as it turns out, so first you must consider what is the strategy you are going to employ in this case. If it is just added and removed items than this code should suffice.

internal class Program
{
    private static ObservableCollection<IItem> oc = new ObservableCollection<IItem>();
    private static readonly long[] crossCheck = {1,2,3};

    private static void Main(string[] args)
    {
        oc.CollectionChanged += oc_CollectionChanged;
        oc.Add(new IItem {Id=1,Amount = 100});
        oc.Add(new IItem {Id=2,Amount = 200});
        oc.Add(new IItem {Id=3,Amount = 300});
        oc.RemoveAt(1);
    }

    private static void oc_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        Console.WriteLine("{0} {1}", e.Action, oc.Sum(s1 => s1.Amount));
        if (crossCheck.SequenceEqual(oc.Select(s1 => s1.Id).Intersect(crossCheck)))
            Console.WriteLine("I have all elements I wanted!");
        if (e.OldItems != null && e.Action.Equals(NotifyCollectionChangedAction.Remove) &&
            e.OldItems.Cast<IItem>().Any(a1 => a1.Id.Equals(2))) Console.WriteLine("I've lost item two");
    }
}

internal class IItem
{
    public long Id { get; set; }
    public int Amount { get; set; }
}

Produces:

Add 100
Add 300
Add 600
I have all elements I wanted!
Remove 400
I've lost item two
Press any key to continue . . .

Of course in your event handler you can process other conditions as needed, for example you probably want to fire some of those data dependent events just once, etc.

Community
  • 1
  • 1
Darek
  • 4,353
  • 28
  • 45