简体   繁体   中英

How do we create an IObservableList<T> from an IObservable<IList<T>>?

Question

How do we go from an IObservable<IReadOnlyList<T>> to an IObservableList<T> (from DynamicData )?

Context

I'm using both Reactive extensions and DynamicData in my project.

I currently have a list of strings that changes over time. I have at as an IObservable<IReadOnlyList<string>> . It yields updates like the following:

  1. ["A", "C", "F"]
  2. ["A", "B", "C", "F"]
  3. ["A", "B", "C"]

I want to convert this into an IObservableList<string> that would yield updates like the following:

  1. Add "A","C","F" at index 0
  2. Add "B" at index 1
  3. Remove "F" from index 3

What I've tried

I've tried using the ToObservableChangeSet extension, but it doesn't have the correct behavior for my case.

Code
Subject<IReadOnlyList<string>> source = new Subject<IReadOnlyList<string>>(); IObservable<IEnumerable<string>> observableOfList = source; IObservable<IChangeSet<string>> observableOfChangeSet = source.ToObservableChangeSet<string>(); observableOfChangeSet .Bind(out ReadOnlyObservableCollection<string> boundCollection) .Subscribe(); ((INotifyCollectionChanged)boundCollection).CollectionChanged += OnCollectionChanged; source.OnNext(new[] { "A", "C", "F" }); source.OnNext(new[] { "A", "B", "C" }); void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { Debug.WriteLine($"[{string.Join(", ", (IEnumerable<string>)sender)}] (operation: {e.Action} at index {e.NewStartingIndex})"); }
Output
Subject<IReadOnlyList<string>> source = new Subject<IReadOnlyList<string>>();
IObservable<IEnumerable<string>> observableOfList = source;
IObservable<IChangeSet<string>> observableOfChangeSet = ObservableChangeSet.Create<string>(list =>
{
    return observableOfList
        .Subscribe(items => list.EditDiff(items, EqualityComparer<string>.Default));
});

observableOfChangeSet
    .Bind(out ReadOnlyObservableCollection<string> boundCollection)
    .Subscribe();

((INotifyCollectionChanged)boundCollection).CollectionChanged += OnCollectionChanged;

source.OnNext(new[] { "A", "C", "F" });
source.OnNext(new[] { "A", "B", "C" });

void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
    Debug.WriteLine($"[{string.Join(", ", (IEnumerable<string>)sender)}] (operation: {e.Action} at index {e.NewStartingIndex})");
}

As we can see [A, C, F, A, B, C] is not the same as [A, B, C] .


I've also tried using EditDiff , but that doesn't preserve the order of the list.

Code
Subject<IReadOnlyList<string>> source = new Subject<IReadOnlyList<string>>(); IObservable<IEnumerable<string>> observableOfList = source; IObservable<IChangeSet<string>> observableOfChangeSet = ObservableChangeSet.Create<string>(list => { return observableOfList .Subscribe(items => list.EditDiff(items, EqualityComparer<string>.Default)); }); observableOfChangeSet .Bind(out ReadOnlyObservableCollection<string> boundCollection) .Subscribe(); ((INotifyCollectionChanged)boundCollection).CollectionChanged += OnCollectionChanged; source.OnNext(new[] { "A", "C", "F" }); source.OnNext(new[] { "A", "B", "C" }); void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { Debug.WriteLine($"[{string.Join(", ", (IEnumerable<string>)sender)}] (operation: {e.Action} at index {e.NewStartingIndex})"); }
Output
[A, C, F] (operation: Reset at index -1) [A, C] (operation: Remove at index -1) [A, C, B] (operation: Add at index 2)

As we can see [A, C, B] is not the same as [A, B, C] .

Thanks!

I am not too familiar with DynamicData, but I believe that this gives something close to the desired outcome:

using DynamicData;
using System;
using System.Collections.Generic;
using System.Reactive.Linq;
using System.Reactive.Subjects;

namespace ConsoleApp3
{
    class Program
    {
        static void Main(string[] args)
        {
            Subject<IReadOnlyList<string>> source = new Subject<IReadOnlyList<string>>();

            var list = ObservableChangeSet.Create<string>(sourceList =>
            {
                return source
                    .Subscribe(newItems =>
                    {
                        sourceList.Edit(items =>
                        {
                            for (var i = 0; i < newItems.Count; i++)
                            {
                                if (items.Count <= i) items.Add(newItems[i]);
                                else
                                {
                                    if (items[i] != newItems[i])
                                    {
                                        items[i] = newItems[i];
                                    }
                                }
                            }
                            while (items.Count > items.Count) items.RemoveAt(items.Count - 1);
                        });
                    });
            }).AsObservableList();

            source.OnNext(new[] { "A", "C", "F" });
            source.OnNext(new[] { "A", "B", "C", "F" });
            source.OnNext(new[] { "A", "B", "C" });

            Console.ReadLine();
        }
    }
}

Unfortunately, I didn't figure out how to get Add "B" at index 1 , but the current output is as follows:

  1. AddRange. 3 changes
  2. 3 Changes
    • Replace. Current: B, Previous: C
    • Replace. Current: C, Previous: F
    • Add. Current: F, Previous: <None>
  3. Remove. Current: C, Previous: <None>

Edit: The following gives the exact output you want, but I have not tested the performance of each (Insert will likely get expensive for larger lists):

using DynamicData;
using System;
using System.Collections.Generic;
using System.Reactive.Linq;
using System.Reactive.Subjects;

namespace ConsoleApp3
{
    class Program
    {
        static void Main(string[] args)
        {
            Subject<IReadOnlyList<string>> source = new Subject<IReadOnlyList<string>>();

            var list = ObservableChangeSet.Create<string>(sourceList =>
            {
                return source
                    .Subscribe(newItems =>
                    {
                        sourceList.Edit(items =>
                        {
                            for (var i = 0; i < newItems.Count; i++)
                            {
                                if (items.Count <= i) items.Add(newItems[i]);
                                else
                                {
                                    if (items[i] != newItems[i])
                                    {
                                        items.Insert(i, newItems[i]);
                                    }
                                }
                            }
                            while (items.Count > newItems.Count) items.RemoveAt(items.Count - 1);
                        });
                    });
            }).AsObservableList();

            source.OnNext(new[] { "A", "C", "F" });
            source.OnNext(new[] { "A", "B", "C", "F" });
            source.OnNext(new[] { "A", "B", "C" });

            Console.ReadLine();
        }
    }
}

  1. AddRange. 3 changes
  2. Add. Current: B, Previous: <None> Index 1
  3. Remove. Current: C, Previous: <None> Index 2

If strings are unique, why don't you use the overload of ToObservableChangeSet where you set the string as the key. That way, if your new list doesn't contain "F", it knows that your existing "F" should be removed.

Maybe there's an easier way but you might have to insert your own index temporarily. If you don't need to track the changes via the bound collection, you can do it from the changeset like this:

Subject<IReadOnlyList<string>> source = new Subject<IReadOnlyList<string>>();
IObservable<IEnumerable<string>> observableOfList = source;
IObservable<IChangeSet<(string,int),string>> observableOfChangeSet = source.Select(x => 
            {
                return x.Select((item, index) => (item, index));
            }).ToObservableChangeSet<(string,int),string>(x=>x.Item1);

observableOfChangeSet
    .Sort(SortExpressionComparer<(string, int)>.Ascending(x => x.Item2))
    .OnItemAdded(x=> Debug.WriteLine($"Item {x.Item1} was added to index {x.Item2}"))
    .OnItemRemoved(x => Debug.WriteLine($"Item {x.Item1} was removed from index {x.Item2}"))
    .OnItemUpdated((x,prev) => Debug.WriteLine($"Item {prev.Item1} at index {prev.Item2} was updated to Item {x.Item1} at index {x.Item2}"))
    .Transform(x=>
        {
            Debug.WriteLine($"Item: {x.Item1} at index: {x.Item2}");
           return x.Item1;
        })
    .Bind(out ReadOnlyObservableCollection<string> boundCollection)
    .Subscribe();

source.OnNext(new[] { "A", "C", "F" });
source.OnNext(new[] { "A", "B", "C" });

My debug output for this code:

Item A was added to index 0
Item C was added to index 1
Item F was added to index 2
Item: A at index: 0
Item: C at index: 1
Item: F at index: 2
Item B was added to index 1
Item F was removed from index 2
Item A at index 0 was updated to Item A at index 0
Item C at index 1 was updated to Item C at index 2
Item: A at index: 0
Item: B at index: 1
Item: C at index: 2

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