Created
January 29, 2022 10:41
-
-
Save wldevries/b3001ddbec87178f360e20090a665170 to your computer and use it in GitHub Desktop.
DynamicData to ImmutableList
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copied and modified from the Dynamic Data project under MIT license | |
// https://github.com/reactivemarbles/DynamicData/blob/main/LICENSE | |
using System; | |
using System.Collections.Generic; | |
using System.Collections.Immutable; | |
using System.ComponentModel; | |
using System.Diagnostics.CodeAnalysis; | |
using System.Reactive.Linq; | |
using DynamicData.Cache.Internal; | |
namespace DynamicData.Immutable | |
{ | |
/// <summary> | |
/// Extensions for ImmutableList | |
/// </summary> | |
public static class ImmutableListEx | |
{ | |
/// <summary> | |
/// Adapt a change set into an observable of immutable lists. | |
/// </summary> | |
public static IObservable<ImmutableList<T>> AsImmutableList<T>(this IObservable<IChangeSet<T>> source) | |
{ | |
if (source is null) | |
{ | |
throw new ArgumentNullException(nameof(source)); | |
} | |
ImmutableList<T> list = ImmutableList.Create<T>(); | |
return source | |
.SelectMany(changes => changes) | |
.Scan( | |
ImmutableList<T>.Empty, | |
(acc, change) => DoUpdate(acc, change, EqualityComparer<T>.Default)); | |
} | |
private static ImmutableList<T> DoUpdate<T>(ImmutableList<T> source, Change<T> item, IEqualityComparer<T> equalityComparer) | |
{ | |
switch (item.Reason) | |
{ | |
case ListChangeReason.Add: | |
{ | |
var change = item.Item; | |
var hasIndex = change.CurrentIndex >= 0; | |
if (hasIndex) | |
{ | |
source = source.Insert(change.CurrentIndex, change.Current); | |
} | |
else | |
{ | |
source = source.Add(change.Current); | |
} | |
break; | |
} | |
case ListChangeReason.AddRange: | |
{ | |
source = item.Range.Index >= 0 | |
? source.InsertRange(item.Range.Index, item.Range) | |
: source.AddRange(item.Range); | |
break; | |
} | |
case ListChangeReason.Clear: | |
{ | |
source = item.Range.Count == source.Count | |
? source.Clear() | |
: source.RemoveRange(item.Range); | |
break; | |
} | |
case ListChangeReason.Replace: | |
{ | |
var change = item.Item; | |
if (change.CurrentIndex >= 0 && change.CurrentIndex == change.PreviousIndex) | |
{ | |
source = source.SetItem(change.CurrentIndex, change.Current); | |
} | |
else | |
{ | |
if (change.PreviousIndex == -1) | |
{ | |
source = source.Remove(change.Previous.Value); | |
} | |
else | |
{ | |
// is this best? or replace + move? | |
source = source.RemoveAt(change.PreviousIndex); | |
} | |
if (change.CurrentIndex == -1) | |
{ | |
source = source.Add(change.Current); | |
} | |
else | |
{ | |
source = source.Insert(change.CurrentIndex, change.Current); | |
} | |
} | |
break; | |
} | |
case ListChangeReason.Refresh: | |
{ | |
// ImmutableList has no notifications, this has no use | |
source = source | |
.RemoveAt(item.Item.CurrentIndex) | |
.Insert(item.Item.CurrentIndex, item.Item.Current); | |
break; | |
} | |
case ListChangeReason.Remove: | |
{ | |
var change = item.Item; | |
bool hasIndex = change.CurrentIndex >= 0; | |
if (hasIndex) | |
{ | |
source = source.RemoveAt(change.CurrentIndex); | |
} | |
else | |
{ | |
int index = source.IndexOf(change.Current, equalityComparer); | |
if (index > -1) | |
{ | |
source = source.RemoveAt(index); | |
} | |
} | |
break; | |
} | |
case ListChangeReason.RemoveRange: | |
{ | |
source = source.RemoveRange(item.Range.Index, item.Range.Count); | |
break; | |
} | |
case ListChangeReason.Moved: | |
{ | |
var change = item.Item; | |
bool hasIndex = change.CurrentIndex >= 0; | |
if (!hasIndex) | |
{ | |
throw new UnspecifiedIndexException("Cannot move as an index was not specified"); | |
} | |
// check this works whatever the index is | |
source = source | |
.RemoveAt(change.PreviousIndex) | |
.Insert(change.CurrentIndex, change.Current); | |
break; | |
} | |
} | |
return source; | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copied and modified from the Dynamic Data project under MIT license | |
// https://github.com/reactivemarbles/DynamicData/blob/main/LICENSE | |
using System; | |
using System.Collections.Immutable; | |
using System.Linq; | |
using System.Reactive.Linq; | |
using DynamicData.Immutable; | |
using DynamicData.Tests.Domain; | |
using FluentAssertions; | |
using Xunit; | |
namespace DynamicData.Tests.Immutable | |
{ | |
public class ImmutableListExFixture : IDisposable | |
{ | |
private readonly IDisposable _binder; | |
private ImmutableList<Person> _collection; | |
private readonly RandomPersonGenerator _generator = new(); | |
private readonly SourceList<Person> _source; | |
public ImmutableListExFixture() | |
{ | |
_collection = ImmutableList<Person>.Empty; | |
_source = new SourceList<Person>(); | |
_binder = _source.Connect() | |
.AsImmutableList() | |
.Subscribe(l => _collection = l); | |
} | |
[Fact] | |
public void AddRange() | |
{ | |
var people = _generator.Take(100).ToList(); | |
_source.AddRange(people); | |
_collection.Count.Should().Be(100, "Should be 100 items in the collection"); | |
_collection.Should().BeEquivalentTo(people, "Collections should be equivalent"); | |
} | |
[Fact] | |
public void AddToSourceAddsToDestination() | |
{ | |
var person = new Person("Adult1", 50); | |
_source.Add(person); | |
_collection.Count.Should().Be(1, "Should be 1 item in the collection"); | |
_collection.First().Should().Be(person, "Should be same person"); | |
} | |
[Fact] | |
public void Clear() | |
{ | |
var people = _generator.Take(100).ToList(); | |
_source.AddRange(people); | |
_source.Clear(); | |
_collection.Count.Should().Be(0, "Should be 0 items in the collection"); | |
} | |
public void Dispose() | |
{ | |
_binder.Dispose(); | |
_source.Dispose(); | |
} | |
[Fact] | |
public void RemoveSourceRemovesFromTheDestination() | |
{ | |
var person = new Person("Adult1", 50); | |
_source.Add(person); | |
_source.Remove(person); | |
_collection.Count.Should().Be(0, "Should be 0 item in the collection"); | |
} | |
[Fact] | |
public void UpdateToSourceUpdatesTheDestination() | |
{ | |
var person = new Person("Adult1", 50); | |
var personUpdated = new Person("Adult1", 51); | |
_source.Add(person); | |
_source.Replace(person, personUpdated); | |
_collection.Count.Should().Be(1, "Should be 1 item in the collection"); | |
_collection.First().Should().Be(personUpdated, "Should be updated person"); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment