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
[A, C, F] (operation: Reset at index -1)
[A, C, F, A] (operation: Add at index 3)
[A, C, F, A, B] (operation: Add at index 4)
[A, C, F, A, B, C] (operation: Add at index 5)
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 ended up writing a custom solution.
It's open source and nuget packages are also available.
To solve the original issue, install the following packages.
CollectionTracking.DynamicData
System.Reactive
Then you can use a method like the following.
using System.Reactive.Linq;
using CollectionTracking;
// ...
public static class DynamicDataExtensions
{
/// <summary>
/// Converts <see cref="IObservable{IEnumerable}"/> to <see cref="IObservableList{T}"/>.
/// Keeps the same ordering, which is why we use .GetOperations and not .EditDiff.
/// </summary>
/// <typeparam name="T">Type of items in list.</typeparam>
/// <param name="observableOfList"><see cref="IObservable{IEnumerable}"/> to convert.</param>
/// <returns>Converted <see cref="IObservableList{T}"/>.</returns>
public static IObservableList<T> ToObservableList<T>(this IObservable<IEnumerable<T>> observableOfList)
{
return observableOfList
.StartWith(Enumerable.Empty<T>())
.Buffer(2, 1)
.Select(lists => lists[0].GetOperations(lists[1]).ToChangeSet())
.AsObservableList();
}
}