Skip to content

Commit 0e1a9fe

Browse files
dchaibRolandPheasant
authored andcommitted
Remove from transform many with projection (#265)
* Add failing unit tests * Add an equality comparer in IChangeSet for lists * Pass the equality comparer to the Clone method directly from TransformMany instead of using IChangeSet * Add equality comparer to other calls to Clone method in TransformMany * Bump minor version
1 parent 2b19227 commit 0e1a9fe

4 files changed

Lines changed: 220 additions & 9 deletions

File tree

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
using DynamicData.Aggregation;
2+
using DynamicData.Binding;
3+
using FluentAssertions;
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Collections.ObjectModel;
7+
using System.Linq;
8+
using System.Text;
9+
using Xunit;
10+
11+
namespace DynamicData.Tests.List
12+
{
13+
public class TransformManyProjectionFixture : IDisposable
14+
{
15+
private readonly ISourceList<ClassWithNestedObservableCollection> _source;
16+
private readonly IObservableList<ProjectedNestedChild> _results;
17+
18+
public TransformManyProjectionFixture()
19+
{
20+
_source = new SourceList<ClassWithNestedObservableCollection>();
21+
22+
_results = _source.Connect()
23+
.AutoRefreshOnObservable(self => self.Children.ToObservableChangeSet())
24+
.TransformMany(parent => parent.Children.Select(c => new ProjectedNestedChild(parent, c)), new ProjectNestedChildEqualityComparer())
25+
.AsObservableList();
26+
}
27+
28+
public void Dispose()
29+
{
30+
_source.Dispose();
31+
}
32+
33+
[Fact]
34+
public void AddRange()
35+
{
36+
var children = new[]
37+
{
38+
new NestedChild("A", "ValueA"),
39+
new NestedChild("B", "ValueB"),
40+
new NestedChild("C", "ValueC"),
41+
new NestedChild("D", "ValueD"),
42+
new NestedChild("E", "ValueE"),
43+
new NestedChild("F", "ValueF")
44+
};
45+
46+
var parents = new[]
47+
{
48+
new ClassWithNestedObservableCollection(1, new[] { children[0], children[1] }),
49+
new ClassWithNestedObservableCollection(2, new[] { children[2], children[3] }),
50+
new ClassWithNestedObservableCollection(3, new[] { children[4] })
51+
};
52+
53+
_source.AddRange(parents);
54+
55+
_results.Count.Should().Be(5);
56+
_results.Items.ShouldBeEquivalentTo(parents.SelectMany(p => p.Children.Take(5).Select(c => new ProjectedNestedChild(p, c))));
57+
}
58+
59+
[Fact]
60+
public void RemoveParent()
61+
{
62+
var children = new[]
63+
{
64+
new NestedChild("A", "ValueA"),
65+
new NestedChild("B", "ValueB"),
66+
new NestedChild("C", "ValueC"),
67+
new NestedChild("D", "ValueD"),
68+
new NestedChild("E", "ValueE"),
69+
new NestedChild("F", "ValueF")
70+
};
71+
72+
var parents = new[]
73+
{
74+
new ClassWithNestedObservableCollection(1, new[] { children[0], children[1] }),
75+
new ClassWithNestedObservableCollection(2, new[] { children[2], children[3] }),
76+
new ClassWithNestedObservableCollection(3, new[] { children[4] })
77+
};
78+
79+
_source.AddRange(parents);
80+
81+
//remove a parent and check children have moved
82+
_source.Remove(parents[0]);
83+
_results.Count.Should().Be(3);
84+
_results.Items.ShouldBeEquivalentTo(parents.Skip(1).SelectMany(p => p.Children.Select(c => new ProjectedNestedChild(p, c))));
85+
}
86+
87+
[Fact]
88+
public void RemoveChild()
89+
{
90+
var children = new[]
91+
{
92+
new NestedChild("A", "ValueA"),
93+
new NestedChild("B", "ValueB"),
94+
new NestedChild("C", "ValueC"),
95+
new NestedChild("D", "ValueD"),
96+
new NestedChild("E", "ValueE"),
97+
new NestedChild("F", "ValueF")
98+
};
99+
100+
var parents = new[]
101+
{
102+
new ClassWithNestedObservableCollection(1, new[] { children[0], children[1] }),
103+
new ClassWithNestedObservableCollection(2, new[] { children[2], children[3] }),
104+
new ClassWithNestedObservableCollection(3, new[] { children[4] })
105+
};
106+
107+
_source.AddRange(parents);
108+
109+
//remove a child
110+
parents[1].Children.Remove(children[3]);
111+
_results.Count.Should().Be(4);
112+
_results.Items.ShouldBeEquivalentTo(parents.SelectMany(p => p.Children.Where(child => child.Name != "D").Select(c => new ProjectedNestedChild(p, c))));
113+
}
114+
115+
private class ProjectedNestedChild
116+
{
117+
public ClassWithNestedObservableCollection Parent { get; }
118+
119+
public NestedChild Child { get; }
120+
121+
public ProjectedNestedChild(ClassWithNestedObservableCollection parent, NestedChild child)
122+
{
123+
Parent = parent;
124+
Child = child;
125+
}
126+
}
127+
128+
private class ProjectNestedChildEqualityComparer : IEqualityComparer<ProjectedNestedChild>
129+
{
130+
public bool Equals(ProjectedNestedChild x, ProjectedNestedChild y)
131+
{
132+
if (x == null || y == null)
133+
return false;
134+
135+
return x.Child.Name == y.Child.Name;
136+
}
137+
138+
public int GetHashCode(ProjectedNestedChild obj)
139+
{
140+
return obj.Child.Name.GetHashCode();
141+
}
142+
}
143+
144+
private class NestedChild
145+
{
146+
public string Name { get; }
147+
public string Value { get; }
148+
149+
public NestedChild(string name, string value)
150+
{
151+
Name = name;
152+
Value = value;
153+
}
154+
}
155+
156+
private class ClassWithNestedObservableCollection
157+
{
158+
public int Id { get; }
159+
public ObservableCollection<NestedChild> Children { get; }
160+
161+
public ClassWithNestedObservableCollection(int id, IEnumerable<NestedChild> animals)
162+
{
163+
Id = id;
164+
Children = new ObservableCollection<NestedChild>(animals);
165+
}
166+
}
167+
}
168+
}

src/DynamicData/List/Internal/TransformMany.cs

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,23 @@ public IObservable<IChangeSet<TDestination>> Run()
9393
return CreateWithChangeset();
9494
}
9595

96-
return _source.Transform(item => new ManyContainer(_manyselector(item).ToArray()), true)
97-
.Select(changes => new ChangeSet<TDestination>(new DestinationEnumerator(changes, _equalityComparer))).NotEmpty();
96+
return Observable.Create<IChangeSet<TDestination>>(observer =>
97+
{
98+
//NB: ChangeAwareList is used internally by dd to capture changes to a list and ensure they can be replayed by subsequent operators
99+
var result = new ChangeAwareList<TDestination>();
100+
101+
return _source.Transform(item => new ManyContainer(_manyselector(item).ToArray()), true)
102+
.Select(changes =>
103+
{
104+
var destinationChanges = new ChangeSet<TDestination>(new DestinationEnumerator(changes, _equalityComparer));
105+
result.Clone(destinationChanges, _equalityComparer);
106+
return result.CaptureChanges();
107+
})
108+
109+
.NotEmpty()
110+
.SubscribeSafe(observer);
111+
}
112+
);
98113
}
99114

100115
private IObservable<IChangeSet<TDestination>> CreateWithChangeset()
@@ -123,15 +138,15 @@ private IObservable<IChangeSet<TDestination>> CreateWithChangeset()
123138

124139
var init = intial.Select(changes =>
125140
{
126-
result.Clone(changes);
141+
result.Clone(changes, _equalityComparer);
127142
return result.CaptureChanges();
128143
});
129144

130145
var subseq = subsequent
131146
.RemoveIndex()
132147
.Select(changes =>
133148
{
134-
result.Clone(changes);
149+
result.Clone(changes, _equalityComparer);
135150
return result.CaptureChanges();
136151
});
137152

src/DynamicData/List/ListEx.cs

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,23 @@ internal static bool MovedWithinRange<T>(this Change<T> source, int startIndex,
4545
/// changes
4646
/// </exception>
4747
public static void Clone<T>(this IList<T> source, IChangeSet<T> changes)
48+
{
49+
Clone(source, changes, null);
50+
}
51+
52+
/// <summary>
53+
/// Clones the list from the specified change set
54+
/// </summary>
55+
/// <typeparam name="T"></typeparam>
56+
/// <param name="source">The source.</param>
57+
/// <param name="changes">The changes.</param>
58+
/// <param name="equalityComparer">An equality comparer to match items in the changes.</param>
59+
/// <exception cref="System.ArgumentNullException">
60+
/// source
61+
/// or
62+
/// changes
63+
/// </exception>
64+
public static void Clone<T>(this IList<T> source, IChangeSet<T> changes, IEqualityComparer<T> equalityComparer)
4865
{
4966
if (source == null)
5067
{
@@ -58,11 +75,11 @@ public static void Clone<T>(this IList<T> source, IChangeSet<T> changes)
5875

5976
foreach (var item in changes)
6077
{
61-
Clone(source, item);
78+
Clone(source, item, equalityComparer ?? EqualityComparer<T>.Default);
6279
}
6380
}
6481

65-
private static void Clone<T>(this IList<T> source, Change<T> item)
82+
private static void Clone<T>(this IList<T> source, Change<T> item, IEqualityComparer<T> equalityComparer)
6683
{
6784
var changeAware = source as ChangeAwareList<T>;
6885

@@ -154,8 +171,19 @@ private static void Clone<T>(this IList<T> source, Change<T> item)
154171
source.RemoveAt(change.CurrentIndex);
155172
}
156173
else
157-
{
158-
source.Remove(change.Current);
174+
{
175+
if (equalityComparer != null)
176+
{
177+
int index = source.IndexOf(change.Current, equalityComparer);
178+
if (index > -1)
179+
{
180+
source.RemoveAt(index);
181+
}
182+
}
183+
else
184+
{
185+
source.Remove(change.Current);
186+
}
159187
}
160188

161189
break;

version.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"version": "6.12",
2+
"version": "6.13",
33
"publicReleaseRefSpec": [
44
"^refs/heads/master$", // we release out of master
55
"^refs/heads/preview/.*", // we release previews

0 commit comments

Comments
 (0)