Skip to content

Instantly share code, notes, and snippets.

@wldevries
Created January 29, 2022 10:41
Show Gist options
  • Save wldevries/b3001ddbec87178f360e20090a665170 to your computer and use it in GitHub Desktop.
Save wldevries/b3001ddbec87178f360e20090a665170 to your computer and use it in GitHub Desktop.
DynamicData to ImmutableList
// 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;
}
}
}
// 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