public void Update(TContext context, BasePrimitive <TItem> current, BasePrimitive <TItem> next) { var currentList = current?.Attributes.GetAttributeOrDefault(_attribute); var nextList = next?.Attributes.GetAttributeOrDefault(_attribute); // get an id to allow for easy viewing long loggingId = 0; if (IsLoggingEnabled.Value) { loggingId = Interlocked.Increment(ref LoggingId); Logging.Instance.LogInformation("CollectionUpdate:Begin {id} current Length:{currentLength} next Length:{nextLength}", loggingId, currentList?.Length ?? 0, nextList?.Length ?? 0); } IEnumerable <Operation <TTarget> > operations; // get a list of operations that transform currentList to nextList IReadOnlyList <Operation <TTarget> > comparisonOperations; if (IsLoggingEnabled.Value) { using var _ = Benchmark.Create((d, __) => { Logging.Instance.LogInformation("CollectionUpdate:Timings {id} duration:{duration}ms, algorithm:{algorithm}", loggingId, d, _collectionComparer.GetType().FriendlyName()); }); comparisonOperations = _collectionComparer.Compare(currentList, nextList); } else { comparisonOperations = _collectionComparer.Compare(currentList, nextList); } if (IsLoggingEnabled.Value) { Logging.Instance.LogInformation("CollectionUpdate:Raw {id} change count:{count}", loggingId, comparisonOperations.Count); } // only perform an attempt at a compaction if there is an insert and a delete both in existence if (comparisonOperations.Count >= 2 && comparisonOperations.Any(f => f.GetType() == typeof(DeleteOp <IPrimitive>)) && comparisonOperations.Any(f => f.GetType() == typeof(InsertOp <IPrimitive>))) { if (IsLoggingEnabled.Value) { Logging.Instance.LogInformation("CollectionUpdate:Compacting Changes"); } // create our collection change compactor which will look to reduce the number of updates made var collectionChangeCompactor = new CollectionChangeCompactor <TTarget>(currentList ?? nextList); foreach (var operation in comparisonOperations) { switch (operation) { case InsertOp <TTarget> op: collectionChangeCompactor.Insert(op.Index, op.Item); break; case DeleteOp <TTarget> op: collectionChangeCompactor.Delete(op.Index); break; case MoveOp <TTarget> op: collectionChangeCompactor.Delete(op.From); collectionChangeCompactor.Insert(op.To, op.Item); break; case UpdateOp <TTarget> op: collectionChangeCompactor.Update(op.Index, op.ToItem); break; } } operations = collectionChangeCompactor.GetChangOps(); } else { operations = comparisonOperations; } // now apply changes foreach (var operation in operations) { switch (operation) { case UpdateOp <TTarget> op: if (IsLoggingEnabled.Value) { Logging.Instance.LogInformation("CollectionUpdate:Update {id} {index}:{item}", loggingId, op.Index, op.FromItem, op.ToItem); } _connector.Update(op.Index, op.FromItem, op.ToItem); break; case DeleteOp <TTarget> op: if (IsLoggingEnabled.Value) { Logging.Instance.LogInformation("CollectionUpdate:Delete {id} {index}:{item}", loggingId, op.Index, op.Item); } _connector.Remove(op.Index, op.Item); break; case InsertOp <TTarget> op: if (IsLoggingEnabled.Value) { Logging.Instance.LogInformation("CollectionUpdate:Insert {id} {index}:{item}", loggingId, op.Index, op.Item); } _connector.Insert(op.Index, op.Item); break; default: throw new ArgumentOutOfRangeException(); } } if (IsLoggingEnabled.Value) { Logging.Instance.LogInformation("CollectionUpdate:End {id}", loggingId); } }