简体   繁体   中英

Switching on IObservable<IEnumerable<IEnumerable<T>>>

I have the following structure:

// source of data
interface IItem
{
    IObservable<string> Changed { get; }
}

interface IItemCollection
{
    List<IItem> Items { get; }
    IObservable<IItem> ItemAdded { get; }
    IObservable<IItem> ItemRemoved { get; }
}

interface IItemCollectionManager
{
    List<IItemCollection> ItemCollectionCollection { get; }
    IObservable<IItemCollection> ItemCollectionAdded { get; }
    IObservable<IItemCollection> ItemCollectionRemoved { get; }
}

// desired result
interface IAggregation
{
    IObservable<string> Changed { get; }
}

The goal here, is for IAggregation to expose a single observable. However, IItem s can be added and removed from each IItemCollection at any time, and, indeed, an IItemCollection can be added or removed from IItemCollectionManager at any time too. Of course, when such an IItemCollection is added, Aggregation should emit values from that one as well, and if an ItemCollection is removed, I no longer want string s from the IItem s in that collection to be emitted. Also, when an Item is added to any IItemCollection , values from its Changed observable should also produce values out of IAggregation 's Changed observable.

Now, it was fairly simple solve this problem when there was just a single IItemCollection , eg like so:

class AggregationImpl : IAggregation 
{
    public AggregationImpl(IItemCollection itemCollection)
    {
        var added = itemCollection.ItemAdded
            .Select(_ => itemCollection.Items);
        var removed = itemCollection.ItemRemoved
            .Select(_ => itemCollection.Items);

        Changed = Observable.Merge(added, removed)
            .StartWith(itemCollection.Items)
            .Select(coll => coll.Select(item => item.Changed).Merge())
            .Switch();
    }

    public IObservable<string> Changed { get; }

}

... the key point here being that I flatten out all the Item 's Changed observables into a single observable with Merge() and then, each time an item is added or removed, I recreate the whole Observable and use Switch() to unsubscribe from the old and subscribe to the new`.

I feel like expanding to include the IItemCollectionManager should be quite straightforward, but I'm not quite sure how to approach it.

I hope this works, or at least sets you down the right path. As testing it seems rather involved, I'm going to wing it. If you have some easy test code, then I'll be happy to test.

First, I don't really like your posted implementation. You're hooking into the ItemAdded and ItemRemoved obeservables, without using the data at all; you're getting the data from the Items property. This can lead to a race condition in a poor implementation, where the event is sent out before the property is updated. I therefore created my own implementation. I also recommend throwing it into an extension method, because that makes life easier later:

public static IObservable<string> ToAggregatedObservable(this IItemCollection itemCollection)
{
    return Observable.Merge(
            itemCollection.ItemAdded.Select(item => (op: "+", item)),
            itemCollection.ItemRemoved.Select(item => (op: "-", item))
        )
        .Scan(ImmutableList<IItem>.Empty.AddRange(itemCollection.Items), (list, t) =>
            t.op == "+"
                ? list.Add(t.item)
                : list.Remove(t.item)
        )
        .Select(l => l.Select(item => item.Changed).Merge())
        .Switch();

}

Forgive the magic string for a second, you can turn it into an enum if you like. We maintain the state of the current items in an ImmutableList inside Scan . When an item gets added/removed, we update the list, then switch the observable.

This same logic can be applied to the collection manager level:

public static IObservable<string> ToAggregatedObservable(this IItemCollectionManager itemCollectionManager)
{
    return Observable.Merge(
            itemCollectionManager.ItemCollectionAdded.Select(itemColl => (op: "+", itemColl)),
            itemCollectionManager.ItemCollectionRemoved.Select(itemColl => (op: "-", itemColl))
        )
        .Scan(ImmutableList<IItemCollection>.Empty.AddRange(itemCollectionManager.ItemCollectionCollection), (list, t) =>
            t.op == "+"
                ? list.Add(t.itemColl)
                : list.Remove(t.itemColl)
        )
        .Select(l => l.Select(itemColl => itemColl.ToAggregatedObservable()).Merge())
        .Switch();
}

Here we're just re-using the first extension method, and using the same adding/removing then switch logic as before.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM