diff --git a/OnlySln.slnf b/OnlySln.slnf new file mode 100644 index 0000000..00b2b3b --- /dev/null +++ b/OnlySln.slnf @@ -0,0 +1,8 @@ +{ + "solution": { + "path": "ObservableCollections.sln", + "projects": [ + "src\\ObservableCollections\\ObservableCollections.csproj" + ] + } +} \ No newline at end of file diff --git a/src/ObservableCollections/IObservableCollection.cs b/src/ObservableCollections/IObservableCollection.cs index de7a544..942ccef 100644 --- a/src/ObservableCollections/IObservableCollection.cs +++ b/src/ObservableCollections/IObservableCollection.cs @@ -12,8 +12,10 @@ namespace ObservableCollections event NotifyCollectionChangedEventHandler? CollectionChanged; ISynchronizedView CreateView(Func transform, bool reverse = false); - ISynchronizedView CreateSortedView(Func transform, IComparer comparer); - ISynchronizedView CreateSortedView(Func transform, IComparer viewComparer); + ISynchronizedView CreateSortedView(Func identitySelector, Func transform, IComparer comparer) + where TKey : notnull; + ISynchronizedView CreateSortedView(Func identitySelector, Func transform, IComparer viewComparer) + where TKey : notnull; } public interface IFreezedCollection @@ -43,4 +45,60 @@ namespace ObservableCollections public interface INotifyCollectionChangedSynchronizedView : ISynchronizedView, INotifyCollectionChanged, INotifyPropertyChanged { } + + public static class ObservableCollectionsExtensions + { + public static ISynchronizedView CreateSortedView(this IObservableCollection source, Func identitySelector, Func transform, Func compareSelector, bool ascending = true) + where TKey : notnull + { + return source.CreateSortedView(identitySelector, transform, new AnonymousComparer(compareSelector, ascending)); + } + + public static ISortableSynchronizedView CreateSortableView(this IFreezedCollection source, Func transform, IComparer initialSort) + { + var view = source.CreateSortableView(transform); + view.Sort(initialSort); + return view; + } + + public static ISortableSynchronizedView CreateSortableView(this IFreezedCollection source, Func transform, IComparer initialViewSort) + { + var view = source.CreateSortableView(transform); + view.Sort(initialViewSort); + return view; + } + + public static ISortableSynchronizedView CreateSortableView(this IFreezedCollection source, Func transform, Func initialCompareSelector, bool ascending = true) + { + var view = source.CreateSortableView(transform); + view.Sort(initialCompareSelector, ascending); + return view; + } + + public static void Sort(this ISortableSynchronizedView source, Func compareSelector, bool ascending = true) + { + source.Sort(new AnonymousComparer(compareSelector, ascending)); + } + + class AnonymousComparer : IComparer + { + readonly Func selector; + readonly int f; + + public AnonymousComparer(Func selector, bool ascending) + { + this.selector = selector; + this.f = ascending ? 1 : -1; + } + + public int Compare(T? x, T? y) + { + if (x == null && y == null) return 0; + if (x == null) return 1 * f; + if (y == null) return -1 * f; + + return Comparer.Default.Compare(selector(x), selector(y)) * f; + } + } + } } \ No newline at end of file diff --git a/src/ObservableCollections/Internal/FreezedView.cs b/src/ObservableCollections/Internal/FreezedView.cs index e9d5f93..e696081 100644 --- a/src/ObservableCollections/Internal/FreezedView.cs +++ b/src/ObservableCollections/Internal/FreezedView.cs @@ -1,4 +1,6 @@ -using System; +#pragma warning disable CS0067 + +using System; using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; diff --git a/src/ObservableCollections/ObservableDictionary.Views.cs b/src/ObservableCollections/ObservableDictionary.Views.cs index 16ada84..3b57cf0 100644 --- a/src/ObservableCollections/ObservableDictionary.Views.cs +++ b/src/ObservableCollections/ObservableDictionary.Views.cs @@ -25,6 +25,17 @@ namespace ObservableCollections return new ViewComparerSortedView(this, transform, viewComparer); } + // identity selector is ignored + ISynchronizedView, TView> IObservableCollection>.CreateSortedView(Func, TKey1> identitySelector, Func, TView> transform, IComparer> comparer) + { + return new SortedView(this, transform, comparer); + } + + ISynchronizedView, TView> IObservableCollection>.CreateSortedView(Func, TKey1> identitySelector, Func, TView> transform, IComparer viewComparer) + { + return new ViewComparerSortedView(this, transform, viewComparer); + } + class View : ISynchronizedView, TView> { readonly ObservableDictionary source; @@ -136,7 +147,7 @@ namespace ObservableCollections { dict.Remove(e.OldItem.Key); var v = selector(e.NewItem); - dict.Add(e.NewItem.Key, (e.NewItem.Value, v)); + dict[e.NewItem.Key] = (e.NewItem.Value, v); filter.Invoke(new KeyValuePair(e.NewItem.Key, e.NewItem.Value), v); } break; @@ -273,7 +284,7 @@ namespace ObservableCollections dict.Remove(k); var v = selector(e.NewItem); var nk = new KeyValuePair(e.NewItem.Key, e.NewItem.Value); - dict.Add(nk, v); + dict[nk] = v; filter.Invoke(nk, v); } break; @@ -419,8 +430,14 @@ namespace ObservableCollections if (viewMap.Remove(e.OldItem.Key, out var view)) { dict.Remove(view); + + var v = selector(e.NewItem); + var k = new KeyValuePair(e.NewItem.Key, e.NewItem.Value); + dict[v] = k; + viewMap[e.NewItem.Key] = v; + filter.Invoke(k, v); } - goto case NotifyCollectionChangedAction.Add; + break; } case NotifyCollectionChangedAction.Reset: { diff --git a/src/ObservableCollections/ObservableDictionary.cs b/src/ObservableCollections/ObservableDictionary.cs index dbbc295..ab02016 100644 --- a/src/ObservableCollections/ObservableDictionary.cs +++ b/src/ObservableCollections/ObservableDictionary.cs @@ -38,12 +38,18 @@ namespace ObservableCollections { lock (SyncRoot) { - var oldValue = dictionary[key]; - dictionary[key] = value; - CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs>.Replace( - new KeyValuePair(key, value), - new KeyValuePair(key, oldValue), - -1)); + if (dictionary.TryGetValue(key, out var oldValue)) + { + dictionary[key] = value; + CollectionChanged?.Invoke(NotifyCollectionChangedEventArgs>.Replace( + new KeyValuePair(key, value), + new KeyValuePair(key, oldValue), + -1)); + } + else + { + Add(key, value); + } } } } diff --git a/src/ObservableCollections/ObservableList.Views.cs b/src/ObservableCollections/ObservableList.Views.cs index b005614..68dc148 100644 --- a/src/ObservableCollections/ObservableList.Views.cs +++ b/src/ObservableCollections/ObservableList.Views.cs @@ -14,14 +14,16 @@ namespace ObservableCollections return new View(this, transform, reverse); } - public ISynchronizedView CreateSortedView(Func transform, IComparer comparer) + public ISynchronizedView CreateSortedView(Func identitySelector, Func transform, IComparer comparer) + where TKey : notnull { - return new SortedView(this, transform, comparer); + return new SortedView(this, identitySelector, transform, comparer); } - public ISynchronizedView CreateSortedView(Func transform, IComparer viewComparer) + public ISynchronizedView CreateSortedView(Func identitySelector, Func transform, IComparer viewComparer) + where TKey : notnull { - return new ViewComparerSortedView(this, transform, viewComparer); + return new ViewComparerSortedView(this, identitySelector, transform, viewComparer); } class View : ISynchronizedView @@ -29,7 +31,7 @@ namespace ObservableCollections readonly ObservableList source; readonly Func selector; readonly bool reverse; - readonly List<(T, TView)> list; + protected readonly List<(T, TView)> list; ISynchronizedViewFilter filter; @@ -52,6 +54,10 @@ namespace ObservableCollections } } + protected virtual void DoSort() + { + } + public int Count { get @@ -201,20 +207,20 @@ namespace ObservableCollections break; } + DoSort(); RoutingCollectionChanged?.Invoke(e); CollectionStateChanged?.Invoke(e.Action); } } } - class SortedView : ISynchronizedView + class SortedView : ISynchronizedView + where TKey : notnull { readonly ObservableList source; - readonly Func selector; - - // SortedList is array-based, SortedDictionary is red-black tree based. - // key as with index to keep uniqueness - readonly SortedDictionary<(T value, int index), TView> list; + readonly Func transform; + readonly Func identitySelector; + readonly SortedDictionary<(T Value, TKey Key), (T Value, TView View)> list; ISynchronizedViewFilter filter; @@ -223,19 +229,20 @@ namespace ObservableCollections public object SyncRoot { get; } = new object(); - public SortedView(ObservableList source, Func selector, IComparer comparer) + public SortedView(ObservableList source, Func identitySelector, Func transform, IComparer comparer) { this.source = source; - this.selector = selector; + this.identitySelector = identitySelector; + this.transform = transform; this.filter = SynchronizedViewFilter.AlwaysTrue; lock (source.SyncRoot) { - var dict = new SortedDictionary<(T, int), TView>(new WithIndexComparer(comparer)); + var dict = new SortedDictionary<(T, TKey), (T, TView)>(new Comparer(comparer)); var count = source.list.Count; for (int i = 0; i < count; i++) { var v = source.list[i]; - dict.Add((v, i), selector(v)); + dict.Add((v, identitySelector(v)), (v, transform(v))); } this.list = dict; @@ -259,9 +266,9 @@ namespace ObservableCollections lock (SyncRoot) { this.filter = filter; - foreach (var (item, view) in list) + foreach (var (_, (value, view)) in list) { - filter.Invoke(item.value, view); + filter.Invoke(value, view); } } } @@ -273,9 +280,9 @@ namespace ObservableCollections this.filter = SynchronizedViewFilter.AlwaysTrue; if (resetAction != null) { - foreach (var (item, view) in list) + foreach (var (_, (value, view)) in list) { - resetAction(item.value, view); + resetAction(value, view); } } } @@ -291,7 +298,7 @@ namespace ObservableCollections public IEnumerator<(T, TView)> GetEnumerator() { - return new SynchronizedViewEnumerator(SyncRoot, list.Select(x => (x.Key.value, x.Value)).GetEnumerator(), filter); + return new SynchronizedViewEnumerator(SyncRoot, list.Select(x => x.Value).GetEnumerator(), filter); } IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); @@ -308,22 +315,24 @@ namespace ObservableCollections switch (e.Action) { case NotifyCollectionChangedAction.Add: - // Add, Insert { + // Add, Insert if (e.IsSingleItem) { - var view = selector(e.NewItem); - list.Add((e.NewItem, e.NewStartingIndex), view); - filter.Invoke(e.NewItem, view); + var value = e.NewItem; + var view = transform(value); + var id = identitySelector(value); + list.Add((value, id), (value, view)); + filter.Invoke(value, view); } else { - var index = e.NewStartingIndex; - foreach (var item in e.NewItems) + foreach (var value in e.NewItems) { - var view = selector(item); - list.Add((item, index++), view); - filter.Invoke(item, view); + var view = transform(value); + var id = identitySelector(value); + list.Add((value, id), (value, view)); + filter.Invoke(value, view); } } } @@ -332,14 +341,16 @@ namespace ObservableCollections { if (e.IsSingleItem) { - list.Remove((e.OldItem, e.OldStartingIndex)); + var value = e.OldItem; + var id = identitySelector(value); + list.Remove((value, id)); } else { - var index = e.OldStartingIndex; - foreach (var item in e.OldItems) + foreach (var value in e.OldItems) { - list.Remove((item, index++)); + var id = identitySelector(value); + list.Remove((value, id)); } } } @@ -349,10 +360,14 @@ namespace ObservableCollections // ObservableList does not support replace range // Replace is remove old item and insert new item(same index on replace, difference index on move). { - var view = selector(e.NewItem); - list.Remove((e.OldItem, e.OldStartingIndex)); - list.Add((e.NewItem, e.NewStartingIndex), view); - filter.Invoke(e.NewItem, view); + var oldValue = e.OldItem; + list.Remove((oldValue, identitySelector(oldValue))); + + var value = e.NewItem; + var view = transform(value); + var id = identitySelector(value); + list.Add((value, id), (value, view)); + filter.Invoke(value, view); } break; case NotifyCollectionChangedAction.Reset: @@ -367,33 +382,36 @@ namespace ObservableCollections } } - class WithIndexComparer : IComparer<(T value, int index)> + sealed class Comparer : IComparer<(T value, TKey id)> { readonly IComparer comparer; - public WithIndexComparer(IComparer comparer) + public Comparer(IComparer comparer) { this.comparer = comparer; } - public int Compare((T value, int index) x, (T value, int index) y) + public int Compare((T value, TKey id) x, (T value, TKey id) y) { - var v = comparer.Compare(x.value, y.value); - if (v == 0) + var compare = comparer.Compare(x.value, y.value); + if (compare == 0) { - v = x.index.CompareTo(y.index); + compare = Comparer.Default.Compare(x.id, y.id); } - return v; + + return compare; } } } - class ViewComparerSortedView : ISynchronizedView + class ViewComparerSortedView : ISynchronizedView + where TKey : notnull { readonly ObservableList source; - readonly Func selector; - readonly Dictionary<(T, int), TView> viewMap; // view-map needs to use in remove. - readonly SortedDictionary<(TView view, int index), T> list; + readonly Func transform; + readonly Func identitySelector; + readonly Dictionary viewMap; // view-map needs to use in remove. + readonly SortedDictionary<(TView View, TKey Key), (T Value, TView View)> list; ISynchronizedViewFilter filter; @@ -402,25 +420,25 @@ namespace ObservableCollections public object SyncRoot { get; } = new object(); - public ViewComparerSortedView(ObservableList source, Func selector, IComparer comparer) + public ViewComparerSortedView(ObservableList source, Func identitySelector, Func transform, IComparer comparer) { this.source = source; - this.selector = selector; - this.viewMap = new Dictionary<(T, int), TView>(source.Count); + this.identitySelector = identitySelector; + this.transform = transform; this.filter = SynchronizedViewFilter.AlwaysTrue; lock (source.SyncRoot) { - var dict = new SortedDictionary<(TView, int), T>(new WithIndexComparer(comparer)); + var dict = new SortedDictionary<(TView, TKey), (T, TView)>(new Comparer(comparer)); + this.viewMap = new Dictionary(source.list.Count); var count = source.list.Count; for (int i = 0; i < count; i++) { - var v = source.list[i]; - var v2 = selector(v); - - dict.Add((v2, i), v); - viewMap.Add((v, i), v2); + var value = source.list[i]; + var view = transform(value); + var id = identitySelector(value); + dict.Add((view, id), (value, view)); + viewMap.Add(id, view); } - this.list = dict; this.source.CollectionChanged += SourceCollectionChanged; } @@ -442,9 +460,9 @@ namespace ObservableCollections lock (SyncRoot) { this.filter = filter; - foreach (var item in list) + foreach (var (_, (value, view)) in list) { - filter.Invoke(item.Value, item.Key.view); + filter.Invoke(value, view); } } } @@ -456,9 +474,9 @@ namespace ObservableCollections this.filter = SynchronizedViewFilter.AlwaysTrue; if (resetAction != null) { - foreach (var item in list) + foreach (var (_, (value, view)) in list) { - resetAction(item.Value, item.Key.view); + resetAction(value, view); } } } @@ -474,7 +492,7 @@ namespace ObservableCollections public IEnumerator<(T, TView)> GetEnumerator() { - return new SynchronizedViewEnumerator(SyncRoot, list.Select(x => (x.Value, x.Key.view)).GetEnumerator(), filter); + return new SynchronizedViewEnumerator(SyncRoot, list.Select(x => x.Value).GetEnumerator(), filter); } IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); @@ -491,25 +509,26 @@ namespace ObservableCollections switch (e.Action) { case NotifyCollectionChangedAction.Add: - // Add, Insert { + // Add, Insert if (e.IsSingleItem) { - var v = selector(e.NewItem); - list.Add((v, e.NewStartingIndex), e.NewItem); - viewMap.Add((e.NewItem, e.NewStartingIndex), v); - filter.Invoke(e.NewItem, v); + var value = e.NewItem; + var view = transform(value); + var id = identitySelector(value); + list.Add((view, id), (value, view)); + viewMap.Add(id, view); + filter.Invoke(value, view); } else { - var index = e.NewStartingIndex; - foreach (var item in e.NewItems) + foreach (var value in e.NewItems) { - var v = selector(item); - list.Add((v, index), item); - viewMap.Add((item, index), v); - filter.Invoke(item, v); - index++; + var view = transform(value); + var id = identitySelector(value); + list.Add((view, id), (value, view)); + viewMap.Add(id, view); + filter.Invoke(value, view); } } } @@ -518,21 +537,22 @@ namespace ObservableCollections { if (e.IsSingleItem) { - if (viewMap.Remove((e.OldItem, e.OldStartingIndex), out var view)) + var value = e.OldItem; + var id = identitySelector(value); + if (viewMap.Remove(id, out var view)) { - list.Remove((view, e.OldStartingIndex)); + list.Remove((view, id)); } } else { - var index = e.OldStartingIndex; - foreach (var item in e.OldItems) + foreach (var value in e.OldItems) { - if (viewMap.Remove((item, index), out var view)) + var id = identitySelector(value); + if (viewMap.Remove(id, out var view)) { - list.Remove((view, index)); + list.Remove((view, id)); } - index++; } } } @@ -540,22 +560,25 @@ namespace ObservableCollections case NotifyCollectionChangedAction.Replace: case NotifyCollectionChangedAction.Move: // ObservableList does not support replace range - // Replace is remove old item and insert new item(same index on replace, diffrence index on move). + // Replace is remove old item and insert new item(same index on replace, difference index on move). { - if (viewMap.Remove((e.OldItem, e.OldStartingIndex), out var oldView)) + var oldValue = e.OldItem; + var oldKey = identitySelector(oldValue); + if (viewMap.Remove(oldKey, out var oldView)) { - list.Remove((oldView, e.OldStartingIndex)); - - var newView = selector(e.NewItem); - list.Add((newView, e.NewStartingIndex), e.NewItem); - viewMap.Add((e.NewItem, e.NewStartingIndex), newView); - filter.Invoke(e.NewItem, newView); + list.Remove((oldView, oldKey)); } + + var value = e.NewItem; + var view = transform(value); + var id = identitySelector(value); + list.Add((view, id), (value, view)); + viewMap.Add(id, view); + filter.Invoke(value, view); } break; case NotifyCollectionChangedAction.Reset: list.Clear(); - viewMap.Clear(); break; default: break; @@ -566,27 +589,26 @@ namespace ObservableCollections } } - class WithIndexComparer : IComparer<(TView value, int index)> + sealed class Comparer : IComparer<(TView view, TKey id)> { readonly IComparer comparer; - public WithIndexComparer(IComparer comparer) + public Comparer(IComparer comparer) { this.comparer = comparer; } - public int Compare((TView value, int index) x, (TView value, int index) y) + public int Compare((TView view, TKey id) x, (TView view, TKey id) y) { - var v = comparer.Compare(x.value, y.value); - if (v == 0) + var compare = comparer.Compare(x.view, y.view); + if (compare == 0) { - v = x.index.CompareTo(y.index); + compare = Comparer.Default.Compare(x.id, y.id); } - return v; + + return compare; } } } - - } } \ No newline at end of file diff --git a/tests/ObservableCollections.Tests/ObservableDictionaryTest.cs b/tests/ObservableCollections.Tests/ObservableDictionaryTest.cs new file mode 100644 index 0000000..170a8c4 --- /dev/null +++ b/tests/ObservableCollections.Tests/ObservableDictionaryTest.cs @@ -0,0 +1,103 @@ +using FluentAssertions; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using Xunit; + +namespace ObservableCollections.Tests +{ + public class ObservableDictionaryTest + { + [Fact] + public void View() + { + var dict = new ObservableDictionary(); + var view = dict.CreateView(x => new ViewContainer(x.Value)); + + dict.Add(10, -10); // 0 + dict.Add(50, -50); // 1 + dict.Add(30, -30); // 2 + dict.Add(20, -20); // 3 + dict.Add(40, -40); // 4 + + void Equal(params int[] expected) + { + dict.Select(x => x.Value).OrderByDescending(x => x).Should().Equal(expected); + view.Select(x => x.Value.Value).OrderByDescending(x => x).Should().Equal(expected); + } + + Equal(-10, -20, -30, -40, -50); + + dict[99] = -100; + Equal(-10, -20, -30, -40, -50, -100); + + dict[10] = -5; + Equal(-5, -20, -30, -40, -50, -100); + + dict.Remove(20); + Equal(-5, -30, -40, -50, -100); + + dict.Clear(); + Equal(new int[0]); + } + + [Fact] + public void ViewSorted() + { + var dict = new ObservableDictionary(); + var view1 = dict.CreateSortedView(x => x.Key, x => new ViewContainer(x.Value), x => x.Value, true); + var view2 = dict.CreateSortedView(x => x.Key, x => new ViewContainer(x.Value), x => x.Value, false); + + dict.Add(10, 10); // 0 + dict.Add(50, 50); // 1 + dict.Add(30, 30); // 2 + dict.Add(20, 20); // 3 + dict.Add(40, 40); // 4 + + void Equal(params int[] expected) + { + dict.Select(x => x.Value).OrderBy(x => x).Should().Equal(expected); + view1.Select(x => x.Value.Value).Should().Equal(expected); + view2.Select(x => x.Value.Value).Should().Equal(expected.OrderByDescending(x => x)); + } + + Equal(10, 20, 30, 40, 50); + + dict[99] = 100; + Equal(10, 20, 30, 40, 50, 100); + + dict[10] = -5; + Equal(-5, 20, 30, 40, 50, 100); + + dict.Remove(20); + Equal(-5, 30, 40, 50, 100); + + dict.Clear(); + Equal(new int[0]); + } + + [Fact] + public void Freezed() + { + var dict = new FreezedDictionary(new Dictionary + { + [10] = 10, + [50] = 50, + [30] = 30, + [20] = 20, + [40] = 40, + [60] = 60 + }); + + var view = dict.CreateSortableView(x => new ViewContainer(x.Value)); + + view.Sort(x => x.Key, true); + view.Select(x => x.Value.Value).Should().Equal(10, 20, 30, 40, 50, 60); + view.Select(x => x.View).Should().Equal(10, 20, 30, 40, 50, 60); + + view.Sort(x => x.Key, false); + view.Select(x => x.Value.Value).Should().Equal(60, 50, 40, 30, 20, 10); + view.Select(x => x.View).Should().Equal(60, 50, 40, 30, 20, 10); + } + } +} diff --git a/tests/ObservableCollections.Tests/ObservableListTest.cs b/tests/ObservableCollections.Tests/ObservableListTest.cs index 38195fb..4f52710 100644 --- a/tests/ObservableCollections.Tests/ObservableListTest.cs +++ b/tests/ObservableCollections.Tests/ObservableListTest.cs @@ -1,4 +1,5 @@ using FluentAssertions; +using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using Xunit; @@ -28,6 +29,13 @@ namespace ObservableCollections.Tests view.Select(x => x.View).Should().Equal(expected.Select(x => new ViewContainer(x))); } + void Equal2(params int[] expected) + { + list.Should().Equal(expected); + view.Select(x => x.Value).Should().Equal(expected); + view.Select(x => x.View).Should().Equal(expected.Select(x => new ViewContainer(x))); + } + Equal(10, 50, 30, 20, 40); reference.Move(3, 1); @@ -37,6 +45,96 @@ namespace ObservableCollections.Tests reference.Insert(2, 99); list.Insert(2, 99); Equal(10, 20, 99, 50, 30, 40); + + reference.RemoveAt(2); + list.RemoveAt(2); + Equal(10, 20, 50, 30, 40); + + reference[3] = 88; + list[3] = 88; + Equal(10, 20, 50, 88, 40); + + reference.Clear(); + list.Clear(); + Equal(new int[0]); + + list.AddRange(new[] { 100, 200, 300 }); + Equal2(100, 200, 300); + + list.InsertRange(1, new[] { 400, 500, 600 }); + Equal2(100, 400, 500, 600, 200, 300); + + list.RemoveRange(2, 2); + Equal2(100, 400, 200, 300); + } + + [Fact] + public void ViewSorted() + { + var list = new ObservableList(); + var view1 = list.CreateSortedView(x => x, x => new ViewContainer(x), comparer: Comparer.Default); + var view2 = list.CreateSortedView(x => x, x => new ViewContainer(x), viewComparer: Comparer>.Default); + var view3 = list.CreateSortedView(x => x, x => new ViewContainer(x), x => x, ascending: true); + var view4 = list.CreateSortedView(x => x, x => new ViewContainer(x), x => x, ascending: false); + + list.Add(10); // 0 + list.Add(50); // 1 + list.Add(30); // 2 + list.Add(20); // 3 + list.Add(40); // 4 + + void Equal(params int[] expected) + { + list.Should().Equal(expected); + + var sorted = expected.OrderBy(x => x).ToArray(); + view1.Select(x => x.Value).Should().Equal(sorted); + view2.Select(x => x.View).Should().Equal(sorted.Select(x => new ViewContainer(x))); + view3.Select(x => x.Value).Should().Equal(sorted); + view4.Select(x => x.Value).Should().Equal(expected.OrderByDescending(x => x).ToArray()); + } + + Equal(10, 50, 30, 20, 40); + + list.Move(3, 1); + Equal(10, 20, 50, 30, 40); + + list.Insert(2, 99); + Equal(10, 20, 99, 50, 30, 40); + + list.RemoveAt(2); + Equal(10, 20, 50, 30, 40); + + list[3] = 88; + Equal(10, 20, 50, 88, 40); + + list.Clear(); + Equal(new int[0]); + + list.AddRange(new[] { 100, 200, 300 }); + Equal(100, 200, 300); + + list.InsertRange(1, new[] { 400, 500, 600 }); + Equal(100, 400, 500, 600, 200, 300); + + list.RemoveRange(2, 2); + Equal(100, 400, 200, 300); + } + + [Fact] + public void Freezed() + { + var list = new FreezedList(new[] { 10, 20, 50, 30, 40, 60 }); + + var view = list.CreateSortableView(x => new ViewContainer(x)); + + view.Sort(x => x, true); + view.Select(x => x.Value).Should().Equal(10, 20, 30, 40, 50, 60); + view.Select(x => x.View).Should().Equal(10, 20, 30, 40, 50, 60); + + view.Sort(x => x, false); + view.Select(x => x.Value).Should().Equal(60, 50, 40, 30, 20, 10); + view.Select(x => x.View).Should().Equal(60, 50, 40, 30, 20, 10); } } } diff --git a/tests/ObservableCollections.Tests/ViewContainer.cs b/tests/ObservableCollections.Tests/ViewContainer.cs index 7b5d9f5..e574a60 100644 --- a/tests/ObservableCollections.Tests/ViewContainer.cs +++ b/tests/ObservableCollections.Tests/ViewContainer.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; namespace ObservableCollections.Tests { - public struct ViewContainer : IEquatable + public struct ViewContainer : IEquatable>, IComparable> { public ViewContainer(T value) { @@ -19,9 +19,14 @@ namespace ObservableCollections.Tests return Value.GetHashCode(); } - public bool Equals(T other) + public int CompareTo(ViewContainer other) { - return EqualityComparer.Default.Equals(Value, other); + return Comparer.Default.Compare(Value, other.Value); + } + + public bool Equals(ViewContainer other) + { + return EqualityComparer.Default.Equals(Value, other.Value); } } }