How do we go from an IObservable<IReadOnlyList<T>>
to an IObservableList<T>
(from DynamicData )?
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:
["A", "C", "F"]
["A", "B", "C", "F"]
["A", "B", "C"]
I want to convert this into an IObservableList<string>
that would yield updates like the following:
I've tried using the ToObservableChangeSet
extension, but it doesn't have the correct behavior for my case.
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.
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:
AddRange. 3 changes
3 Changes
Replace. Current: B, Previous: C
Replace. Current: C, Previous: F
Add. Current: F, Previous: <None>
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();
}
}
}
AddRange. 3 changes
Add. Current: B, Previous: <None> Index 1
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.