public IChangeSet <T> Write(IChangeSet <T> changes) { if (changes == null) { throw new ArgumentNullException(nameof(changes)); } IChangeSet <T> result; lock (_locker) { _data.Clone(changes); result = _data.CaptureChanges(); } return(result); }
private IObservable <IChangeSet <TDestination> > CreateWithChangeset() { return(Observable.Create <IChangeSet <TDestination> >(observer => { var result = new ChangeAwareList <TDestination>(); var transformed = _source.Transform(t => { var locker = new object(); var collection = _manyselector(t); var changes = _childChanges(t).Synchronize(locker).Skip(1); return new ManyContainer(collection, changes); }) .Publish(); var outerLock = new object(); var intial = transformed .Synchronize(outerLock) .Select(changes => new ChangeSet <TDestination>(new DestinationEnumerator(changes, _equalityComparer))); var subsequent = transformed .MergeMany(x => x.Changes) .Synchronize(outerLock); var allChanges = intial.Merge(subsequent).Select(changes => { result.Clone(changes); return result.CaptureChanges(); }); return new CompositeDisposable(allChanges.SubscribeSafe(observer), transformed.Connect()); })); }
public IObservable <IChangeSet <TDestination> > Run() { if (_childChanges != null) { return(CreateWithChangeset()); } return(Observable.Create <IChangeSet <TDestination> >(observer => { //NB: ChangeAwareList is used internally by dd to capture changes to a list and ensure they can be replayed by subsequent operators var result = new ChangeAwareList <TDestination>(); return _source.Transform(item => new ManyContainer(_manyselector(item).ToArray()), true) .Select(changes => { var destinationChanges = new ChangeSet <TDestination>(new DestinationEnumerator(changes, _equalityComparer)); result.Clone(destinationChanges, _equalityComparer); return result.CaptureChanges(); }) .NotEmpty() .SubscribeSafe(observer); } )); }
private IChangeSet <IGrouping <TObject, TGroupKey> > CreateChangeSet(ChangeAwareList <IGrouping <TObject, TGroupKey> > result, IDictionary <TGroupKey, GroupContainer> allGroupings, IDictionary <TGroupKey, IGrouping <TObject, TGroupKey> > initialStateOfGroups) { //Now maintain target list foreach (var intialGroup in initialStateOfGroups) { var key = intialGroup.Key; var current = allGroupings[intialGroup.Key]; if (current.List.Count == 0) { //remove if empty allGroupings.Remove(key); result.Remove(intialGroup.Value); } else { var currentState = GetGroupState(current); if (intialGroup.Value.Count == 0) { //an add result.Add(currentState); } else { //a replace (or add if the old group has already been removed) result.Replace(intialGroup.Value, currentState); } } } return(result.CaptureChanges()); }
/// <summary> /// Convert a binding list into an observable change set. /// Change set observes list change events. /// </summary> /// <typeparam name="TCollection">The collection type.</typeparam> /// <typeparam name="T">The type of the object.</typeparam> /// <param name="source">The source.</param> /// <returns>An observable which emits change set values.</returns> /// <exception cref="System.ArgumentNullException">source.</exception> public static IObservable <IChangeSet <T> > ToObservableChangeSet <TCollection, T>(this TCollection source) where TCollection : IBindingList, IEnumerable <T> { if (source is null) { throw new ArgumentNullException(nameof(source)); } return(Observable.Create <IChangeSet <T> >( observer => { var data = new ChangeAwareList <T>(source); if (data.Count > 0) { observer.OnNext(data.CaptureChanges()); } return source.ObserveCollectionChanges().Scan( data, (list, args) => { var changes = args.EventArgs; switch (changes.ListChangedType) { case ListChangedType.ItemAdded when source[changes.NewIndex] is T newItem: { list.Add(newItem); break; }
public IObservable <IChangeSet <T> > Run() { return(Observable.Create <IChangeSet <T> >(observer => { var locker = new object(); Func <T, bool> predicate = t => false; var all = new List <ItemWithMatch>(); var filtered = new ChangeAwareList <ItemWithMatch>(); //requery when predicate changes var predicateChanged = _predicates .Synchronize(locker) .Select(newPredicate => { predicate = newPredicate; Requery(predicate, all, filtered); return filtered.CaptureChanges(); }); /* * Apply the transform operator so 'IsMatch' state can be evalutated and captured one time only * This is to eliminate the need to re-apply the predicate when determining whether an item was previously matched, * which is essential when we have mutable state */ //Need to get item by index and store it in the transform var filteredResult = _source .Synchronize(locker) .Transform <T, ItemWithMatch>((t, previous, idx) => { var wasMatch = previous.ConvertOr(p => p.IsMatch, () => false); return new ItemWithMatch(t, idx, predicate(t), wasMatch); }, true) .Select(changes => { all.Clone(changes); //keep track of all changes Process(filtered, changes); return filtered.CaptureChanges(); }); return predicateChanged.Merge(filteredResult) .NotEmpty() .Select(changes => changes.Transform(iwm => iwm.Item)) // use convert, not transform .SubscribeSafe(observer); })); }
public IObservable <IChangeSet <T> > Run() { return(Observable.Create <IChangeSet <T> >(observer => { long orderItemWasAdded = -1; var locker = new object(); var sourceList = new ChangeAwareList <ExpirableItem <T> >(); var sizeLimited = _source.Synchronize(locker) .Scan(sourceList, (state, latest) => { var items = latest.AsArray(); var expirable = items.Select(t => CreateExpirableItem(t, ref orderItemWasAdded)); if (items.Length == 1) { sourceList.Add(expirable); } else { sourceList.AddRange(expirable); } if (_limitSizeTo > 0 && state.Count > _limitSizeTo) { //remove oldest items [these will always be the first x in the list] var toRemove = state.Count - _limitSizeTo; state.RemoveRange(0, toRemove); } return state; }) .Select(state => state.CaptureChanges()) .Publish(); var timeLimited = (_expireAfter == null ? Observable.Never <IChangeSet <ExpirableItem <T> > >() : sizeLimited) .Filter(ei => ei.ExpireAt != DateTime.MaxValue) .GroupWithImmutableState(ei => ei.ExpireAt) .MergeMany(grouping => { var expireAt = grouping.Key.Subtract(_scheduler.Now.DateTime); return Observable.Timer(expireAt, _scheduler).Select(_ => grouping); }) .Synchronize(locker) .Select(grouping => { sourceList.RemoveMany(grouping.Items); return sourceList.CaptureChanges(); }); var publisher = sizeLimited .Merge(timeLimited) .Cast(ei => ei.Item) .NotEmpty() .SubscribeSafe(observer); return new CompositeDisposable(publisher, sizeLimited.Connect()); })); }
private IChangeSet <ItemWithMatch> Requery(Func <T, bool> predicate, List <ItemWithMatch> all, ChangeAwareList <ItemWithMatch> filtered) { if (all.Count == 0) { return(ChangeSet <ItemWithMatch> .Empty); } if (_policy == ListFilterPolicy.ClearAndReplace) { var itemsWithMatch = all.Select(iwm => new ItemWithMatch(iwm.Item, predicate(iwm.Item), iwm.IsMatch)).ToList(); //mark items as matched? filtered.Clear(); filtered.AddRange(itemsWithMatch.Where(iwm => iwm.IsMatch)); //reset state for all items all.Clear(); all.AddRange(itemsWithMatch); return(filtered.CaptureChanges()); } var toAdd = new List <ItemWithMatch>(all.Count); var toRemove = new List <ItemWithMatch>(all.Count); for (int i = 0; i < all.Count; i++) { var original = all[i]; var newItem = new ItemWithMatch(original.Item, predicate(original.Item), original.IsMatch); all[i] = newItem; if (newItem.IsMatch && !newItem.WasMatch) { toAdd.Add(newItem); } else if (!newItem.IsMatch && newItem.WasMatch) { toRemove.Add(newItem); } } filtered.RemoveMany(toRemove); filtered.AddRange(toAdd); return(filtered.CaptureChanges()); }
private IChangeSet <T> Reset(List <T> original, ChangeAwareList <T> target) { var sorted = original.OrderBy(t => t, _comparer).ToList(); target.Clear(); target.AddRange(sorted); return(target.CaptureChanges()); }
private IChangeSet <T> ChangeComparer(ChangeAwareList <T> target, IComparer <T> comparer) { _comparer = comparer; var sorted = target.OrderBy(t => t, _comparer).ToList(); target.Clear(); target.AddRange(sorted); return(target.CaptureChanges()); }
/// <summary> /// Convert a binding list into an observable change set /// </summary> /// <typeparam name="T">The type of the object.</typeparam> /// <typeparam name="TCollection"></typeparam> /// <param name="source">The source.</param> /// <returns></returns> /// <exception cref="System.ArgumentNullException">source</exception> public static IObservable <IChangeSet <T> > ToObservableChangeSet <TCollection, T>(this TCollection source) where TCollection : IBindingList, IEnumerable <T> { if (source == null) { throw new ArgumentNullException(nameof(source)); } return(Observable.Create <IChangeSet <T> >(observer => { var data = new ChangeAwareList <T>(source); if (data.Count > 0) { observer.OnNext(data.CaptureChanges()); } return source.ObserveCollectionChanges() .Scan(data, (list, args) => { var changes = args.EventArgs; switch (changes.ListChangedType) { case ListChangedType.ItemAdded: { list.Add((T)source[changes.NewIndex]); break; } case ListChangedType.ItemDeleted: { list.RemoveAt(changes.NewIndex); break; } case ListChangedType.ItemChanged: { list[changes.NewIndex] = (T)source[changes.NewIndex]; break; } case ListChangedType.Reset: { list.Clear(); list.AddRange(source); break; } } return list; }) .Select(list => list.CaptureChanges()) .SubscribeSafe(observer); })); }
public IChangeSet <T> Write(IChangeSet <T> changes) { if (changes == null) { throw new ArgumentNullException(nameof(changes)); } IChangeSet <T> result; _lock.EnterWriteLock(); try { _data.Clone(changes); result = _data.CaptureChanges(); } finally { _lock.ExitWriteLock(); } return(result); }
private static IChangeSet <T> Virtualise(List <T> all, ChangeAwareList <T> virtualised, IVirtualRequest request, IChangeSet <T> changeset = null) { if (changeset != null) { all.Clone(changeset); } var previous = virtualised; var current = all.Skip(request.StartIndex) .Take(request.Size) .ToList(); var adds = current.Except(previous); var removes = previous.Except(current); virtualised.RemoveMany(removes); adds.ForEach(t => { var index = current.IndexOf(t); virtualised.Insert(index, t); }); var moves = changeset.EmptyIfNull() .Where(change => change.Reason == ListChangeReason.Moved && change.MovedWithinRange(request.StartIndex, request.StartIndex + request.Size)); foreach (var change in moves) { //check whether an item has moved within the same page var currentIndex = change.Item.CurrentIndex - request.StartIndex; var previousIndex = change.Item.PreviousIndex - request.StartIndex; virtualised.Move(previousIndex, currentIndex); } //find replaces [Is this ever the case that it can be reached] for (var i = 0; i < current.Count; i++) { var currentItem = current[i]; var previousItem = previous[i]; if (ReferenceEquals(currentItem, previousItem)) { continue; } var index = virtualised.IndexOf(currentItem); virtualised.Move(i, index); } return(virtualised.CaptureChanges()); }
private IChangeSet <T> Process(ChangeAwareList <T> target, IChangeSet <T> changes) { // if all removes and not Clear, then more efficient to try clear range if (changes.TotalChanges == changes.Removes && changes.All(c => c.Reason != ListChangeReason.Clear) && changes.Removes > 1) { var removed = changes.Unified().Select(u => u.Current); target.RemoveMany(removed); return(target.CaptureChanges()); } return(ProcessImpl(target, changes)); }
public IObservable <IChangeSet <T> > Run() { return(Observable.Create <IChangeSet <T> >(observer => { var locker = new object(); Func <T, bool> predicate = t => false; var all = new List <ItemWithMatch>(); var filtered = new ChangeAwareList <ItemWithMatch>(); //requery when predicate changes var predicateChanged = _predicates.Synchronize(locker) .Select(newPredicate => { predicate = newPredicate; Requery(predicate, all, filtered); return filtered.CaptureChanges(); }); /* * Apply the transform operator so 'IsMatch' state can be evalutated and captured one time only * This is to eliminate the need to re-apply the predicate when determining whether an item was previously matched */ var filteredResult = _source.Synchronize(locker) .Transform(t => new ItemWithMatch(t, predicate(t))) .Select(changes => { all.Clone(changes); //keep track of all changes filtered.Filter(changes, iwm => iwm.IsMatch); //maintain filtered result return filtered.CaptureChanges(); }); return predicateChanged.Merge(filteredResult) .NotEmpty() .Select(changes => changes.Transform(iwm => iwm.Item)) .SubscribeSafe(observer); })); }
public Continuation <IChangeSet <T> > Write(IChangeSet <T> changes) { if (changes == null) { throw new ArgumentNullException(nameof(changes)); } IChangeSet <T> result; lock (_locker) { try { _data.Clone(changes); result = _data.CaptureChanges(); } catch (Exception ex) { return(new Continuation <IChangeSet <T> >(ex)); } } return(new Continuation <IChangeSet <T> >(result)); }
private IChangeSet <T> ChangeComparer(ChangeAwareList <T> target, IComparer <T> comparer) { _comparer = comparer; if (_resetThreshold > 0 && target.Count <= _resetThreshold) { return(Reorder(target)); } var sorted = target.OrderBy(t => t, _comparer).ToList(); target.Clear(); target.AddRange(sorted); return(target.CaptureChanges()); }
private IChangeSet <IGroup <TObject, TGroupKey> > Regroup(ChangeAwareList <IGroup <TObject, TGroupKey> > result, IDictionary <TGroupKey, Group <TObject, TGroupKey> > groupCollection, IReadOnlyCollection <ItemWithValue <TObject, TGroupKey> > currentItems) { //TODO: We need to update ItemWithValue> foreach (var itemWithValue in currentItems) { var currentGroupKey = itemWithValue.Value; var newGroupKey = _groupSelector(itemWithValue.Item); if (newGroupKey.Equals(currentGroupKey)) { continue; } //remove from the old group var currentGroupLookup = GetCache(groupCollection, currentGroupKey); var currentGroupCache = currentGroupLookup.Group; currentGroupCache.Edit(innerList => innerList.Remove(itemWithValue.Item)); if (currentGroupCache.List.Count == 0) { groupCollection.Remove(currentGroupKey); result.Remove(currentGroupCache); } //Mark the old item with the new cache group itemWithValue.Value = newGroupKey; //add to the new group var newGroupLookup = GetCache(groupCollection, newGroupKey); var newGroupCache = newGroupLookup.Group; newGroupCache.Edit(innerList => innerList.Add(itemWithValue.Item)); if (newGroupLookup.WasCreated) { result.Add(newGroupCache); } } return(result.CaptureChanges()); }
/// <summary> /// Convert an observable collection into an observable change set. /// Change set observes collection change events. /// </summary> /// <typeparam name="TCollection">The type of collection.</typeparam> /// <typeparam name="T">The type of the object.</typeparam> /// <param name="source">The source.</param> /// <returns>An observable that emits the change set.</returns> /// <exception cref="System.ArgumentNullException">source.</exception> public static IObservable <IChangeSet <T> > ToObservableChangeSet <TCollection, T>(this TCollection source) where TCollection : INotifyCollectionChanged, IEnumerable <T> { if (source is null) { throw new ArgumentNullException(nameof(source)); } return(Observable.Create <IChangeSet <T> >( observer => { var data = new ChangeAwareList <T>(source); if (data.Count > 0) { observer.OnNext(data.CaptureChanges()); } return source.ObserveCollectionChanges().Scan( data, (list, args) => { var changes = args.EventArgs; switch (changes.Action) { case NotifyCollectionChangedAction.Add when changes.NewItems is not null: { if (changes.NewItems.Count == 1 && changes.NewItems[0] is T item) { list.Insert(changes.NewStartingIndex, item); } else { list.InsertRange(changes.NewItems.Cast <T>(), changes.NewStartingIndex); } break; }
private IChangeSet <T> Reorder(ChangeAwareList <T> target) { int index = -1; foreach (var item in target.OrderBy(t => t, _comparer).ToList()) { index++; var existing = target[index]; // if item is in the same place, if (ReferenceEquals(item, existing)) { continue; } // Cannot use binary search as Resort is implicit of a mutable change var old = target.IndexOf(item); target.Move(old, index); } return(target.CaptureChanges()); }
/// <summary> /// Convert an observable collection into an observable change set /// </summary> /// <typeparam name="T">The type of the object.</typeparam> /// <typeparam name="TCollection"></typeparam> /// <param name="source">The source.</param> /// <returns></returns> /// <exception cref="System.ArgumentNullException">source</exception> public static IObservable <IChangeSet <T> > ToObservableChangeSet <TCollection, T>(this TCollection source) where TCollection : INotifyCollectionChanged, IEnumerable <T> { if (source == null) { throw new ArgumentNullException(nameof(source)); } return(Observable.Create <IChangeSet <T> >(observer => { var data = new ChangeAwareList <T>(source); if (data.Count > 0) { observer.OnNext(data.CaptureChanges()); } return source.ObserveCollectionChanges() .Scan(data, (list, args) => { var changes = args.EventArgs; switch (changes.Action) { case NotifyCollectionChangedAction.Add: { if (changes.NewItems.Count == 1) { list.Insert(changes.NewStartingIndex, (T)changes.NewItems[0]); } else { list.InsertRange(changes.NewItems.Cast <T>(), changes.NewStartingIndex); } break; } case NotifyCollectionChangedAction.Remove: { if (changes.OldItems.Count == 1) { list.RemoveAt(changes.OldStartingIndex); } else { list.RemoveRange(changes.OldStartingIndex, changes.OldItems.Count); } break; } case NotifyCollectionChangedAction.Replace: { list[changes.NewStartingIndex] = (T)changes.NewItems[0]; break; } case NotifyCollectionChangedAction.Reset: { list.Clear(); list.AddRange(source); break; } case NotifyCollectionChangedAction.Move: { list.Move(changes.OldStartingIndex, changes.NewStartingIndex); break; } } return list; }) .Select(list => list.CaptureChanges()) .SubscribeSafe(observer); })); }
private IChangeSet <T> ProcessImpl(ChangeAwareList <T> target, IChangeSet <T> changes) { var refreshes = new List <T>(changes.Refreshes); foreach (var change in changes) { switch (change.Reason) { case ListChangeReason.Add: { var current = change.Item.Current; Insert(target, current); break; } case ListChangeReason.AddRange: { var ordered = change.Range.OrderBy(t => t, _comparer).ToList(); if (target.Count == 0) { target.AddRange(ordered); } else { ordered.ForEach(item => Insert(target, item)); } break; } case ListChangeReason.Remove: { var current = change.Item.Current; Remove(target, current); break; } case ListChangeReason.Refresh: { // add to refresh list so position can be calculated refreshes.Add(change.Item.Current); // add to current list so downstream operators can receive a refresh // notification, so get the latest index and pass the index up the chain var indexed = target.IndexOfOptional(change.Item.Current).ValueOrThrow(() => new SortException($"Cannot find index of {typeof(T).Name} -> {change.Item.Current}. Expected to be in the list")); target.Refresh(indexed.Item, indexed.Index); break; } case ListChangeReason.Replace: { var current = change.Item.Current; // TODO: check whether an item should stay in the same position // i.e. update and move Remove(target, change.Item.Previous.Value); Insert(target, current); break; } case ListChangeReason.RemoveRange: { target.RemoveMany(change.Range); break; } case ListChangeReason.Clear: { target.Clear(); break; } } } // Now deal with refreshes [can be expensive] foreach (var item in refreshes) { var old = target.IndexOf(item); if (old == -1) { continue; } int newPosition = GetInsertPositionLinear(target, item); if (old < newPosition) { newPosition--; } if (old == newPosition) { continue; } target.Move(old, newPosition); } return(target.CaptureChanges()); }
private IChangeSet <T> ProcessImpl(ChangeAwareList <T> target, IChangeSet <T> changes) { if (_comparer == null) { target.Clone(changes); return(target.CaptureChanges()); } foreach (var change in changes) { switch (change.Reason) { case ListChangeReason.Add: { var current = change.Item.Current; Insert(target, current); break; } case ListChangeReason.AddRange: { var ordered = change.Range.OrderBy(t => t, _comparer).ToList(); if (target.Count == 0) { target.AddRange(ordered); } else { ordered.ForEach(item => Insert(target, item)); } break; } case ListChangeReason.Replace: { var current = change.Item.Current; //TODO: check whether an item should stay in the same position //i.e. update and move Remove(target, change.Item.Previous.Value); Insert(target, current); break; } case ListChangeReason.Remove: { var current = change.Item.Current; Remove(target, current); break; } case ListChangeReason.RemoveRange: { target.RemoveMany(change.Range); break; } case ListChangeReason.Clear: { target.Clear(); break; } } } return(target.CaptureChanges()); }
private IChangeSet <ItemWithMatch> Process(ChangeAwareList <ItemWithMatch> filtered, IChangeSet <ItemWithMatch> changes) { //Maintain all items as well as filtered list. This enables us to a) requery when the predicate changes b) check the previous state when Refresh is called foreach (var item in changes) { switch (item.Reason) { case ListChangeReason.Add: { var change = item.Item; if (change.Current.IsMatch) { filtered.Add(change.Current); } break; } case ListChangeReason.AddRange: { var matches = item.Range.Where(t => t.IsMatch).ToList(); filtered.AddRange(matches); break; } case ListChangeReason.Replace: { var change = item.Item; var match = change.Current.IsMatch; var wasMatch = item.Item.Current.WasMatch; if (match) { if (wasMatch) { //an update, so get the latest index and pass the index up the chain var previous = filtered.Select(x => x.Item) .IndexOfOptional(change.Previous.Value.Item) .ValueOrThrow(() => new InvalidOperationException($"Cannot find index of {typeof(T).Name} -> {change.Previous.Value}. Expected to be in the list")); //replace inline filtered[previous.Index] = change.Current; } else { filtered.Add(change.Current); } } else { if (wasMatch) { filtered.Remove(change.Previous.Value); } } break; } case ListChangeReason.Refresh: { var change = item.Item; var match = change.Current.IsMatch; var wasMatch = item.Item.Current.WasMatch; if (match) { if (wasMatch) { //an update, so get the latest index and pass the index up the chain var previous = filtered.Select(x => x.Item) .IndexOfOptional(change.Current.Item) .ValueOrThrow(() => new InvalidOperationException($"Cannot find index of {typeof(T).Name} -> {change.Previous.Value}. Expected to be in the list")); filtered.RefreshAt(previous.Index); } else { filtered.Add(change.Current); } } else { if (wasMatch) { filtered.Remove(change.Current); } } break; } case ListChangeReason.Remove: { filtered.Remove(item.Item.Current); break; } case ListChangeReason.RemoveRange: { filtered.RemoveMany(item.Range); break; } case ListChangeReason.Clear: { filtered.ClearOrRemoveMany(item); break; } } } return(filtered.CaptureChanges()); }
public void Add() { _source.Add(1); var changes = _source.CaptureChanges(); _clone.Clone(changes); //assert collection _clone.Should().BeEquivalentTo(_source); }
public void Add() { _list.Add(1); //assert changes var changes = _list.CaptureChanges(); changes.Count.Should().Be(1); changes.Adds.Should().Be(1); changes.First().Item.Current.Should().Be(1); //assert collection _list.ShouldAllBeEquivalentTo(Enumerable.Range(1, 1)); }
public void Add() { _source.Add(1); var changes = _source.CaptureChanges(); _clone.Clone(changes); //assert collection CollectionAssert.AreEqual(_source, _clone); }
private IChangeSet <IGroup <TObject, TGroupKey> > Process(ChangeAwareList <IGroup <TObject, TGroupKey> > result, IDictionary <TGroupKey, Group <TObject, TGroupKey> > groupCollection, IChangeSet <ItemWithGroupKey> changes) { foreach (var grouping in changes.Unified().GroupBy(change => change.Current.Group)) { //lookup group and if created, add to result set var currentGroup = grouping.Key; var lookup = GetCache(groupCollection, currentGroup); var groupCache = lookup.Group; if (lookup.WasCreated) { result.Add(groupCache); } //start a group edit session, so all changes are batched groupCache.Edit( list => { //iterate through the group's items and process foreach (var change in grouping) { switch (change.Reason) { case ListChangeReason.Add: { list.Add(change.Current.Item); break; } case ListChangeReason.Replace: { var previousItem = change.Previous.Value.Item; var previousGroup = change.Previous.Value.Group; //check whether an item changing has resulted in a different group if (previousGroup.Equals(currentGroup)) { //find and replace var index = list.IndexOf(previousItem, ReferenceEqualityComparer <TObject> .Instance); list[index] = change.Current.Item; } else { //add to new group list.Add(change.Current.Item); //remove from old group groupCollection.Lookup(previousGroup) .IfHasValue(g => { g.Edit(oldList => oldList.Remove(previousItem)); if (g.List.Count != 0) { return; } groupCollection.Remove(g.GroupKey); result.Remove(g); }); } break; } case ListChangeReason.Refresh: { //1. Check whether item was in the group and should not be now (or vice versa) var currentItem = change.Current.Item; var previousGroup = change.Current.PrevousGroup.Value; //check whether an item changing has resulted in a different group if (previousGroup.Equals(currentGroup)) { // Propagate refresh eventt var cal = (ChangeAwareList <TObject>)list; cal.Refresh(currentItem); } else { //add to new group list.Add(currentItem); //remove from old group if empty groupCollection.Lookup(previousGroup) .IfHasValue(g => { g.Edit(oldList => oldList.Remove(currentItem)); if (g.List.Count != 0) { return; } groupCollection.Remove(g.GroupKey); result.Remove(g); }); } break; } case ListChangeReason.Remove: { list.Remove(change.Current.Item); break; } case ListChangeReason.Clear: { list.Clear(); break; } } } }); if (groupCache.List.Count == 0) { groupCollection.Remove(groupCache.GroupKey); result.Remove(groupCache); } } return(result.CaptureChanges()); }
private static IChangeSet <TValue> Process(Dictionary <TValue, int> values, ChangeAwareList <TValue> result, IChangeSet <ItemWithMatch> changes) { void AddAction(TValue value) => values.Lookup(value) .IfHasValue(count => values[value] = count + 1) .Else(() => { values[value] = 1; result.Add(value); }); void RemoveAction(TValue value) { var counter = values.Lookup(value); if (!counter.HasValue) { return; } //decrement counter var newCount = counter.Value - 1; values[value] = newCount; if (newCount != 0) { return; } //if there are none, then remove and notify result.Remove(value); values.Remove(value); } foreach (var change in changes) { switch (change.Reason) { case ListChangeReason.Add: { var value = change.Item.Current.Value; AddAction(value); break; } case ListChangeReason.AddRange: { change.Range.Select(item => item.Value).ForEach(AddAction); break; } case ListChangeReason.Refresh: { var value = change.Item.Current.Value; var previous = change.Item.Current.Previous; if (value.Equals(previous)) { continue; } RemoveAction(previous); AddAction(value); break; } case ListChangeReason.Replace: { var value = change.Item.Current.Value; var previous = change.Item.Previous.Value.Value; if (value.Equals(previous)) { continue; } RemoveAction(previous); AddAction(value); break; } case ListChangeReason.Remove: { var previous = change.Item.Current.Value; RemoveAction(previous); break; } case ListChangeReason.RemoveRange: { change.Range.Select(item => item.Value).ForEach(RemoveAction); break; } case ListChangeReason.Clear: { result.Clear(); values.Clear(); break; } } } return(result.CaptureChanges()); }
public void Add() { _list.Add(1); //assert changes var changes = _list.CaptureChanges(); Assert.AreEqual(1, changes.Count); Assert.AreEqual(1, changes.Adds); Assert.AreEqual(1, changes.First().Item.Current); //assert collection CollectionAssert.AreEqual(Enumerable.Range(1, 1), _list); }