dotnet icon indicating copy to clipboard operation
dotnet copied to clipboard

[Feature] Add Helper Extension `IList<T>.SyncWith(IList<T> another, IEqualityComparer<T> comparer = null)`

Open jingkecn opened this issue 2 years ago • 4 comments

Describe the problem

This might not be quite requiring, but it will indeed help a lot in some cases where we need to update the entire list ListViewBase.ItemSource without blinking the UI.

For example:

var data = await SomeDataService.FetchDataAsync() // IList<TDataType>;
var oc = new ObservableCollection<TDataType>(data);
MyListView.ItemSource = oc; // Update the entire list.

Each time we update the entire data list (with data binding or not), the UI will blink, this is not terrible but it would be better if we can have an auto diff tool to make it visually updated with only different elements.

Describe the solution

A helper extension IList<T>.SyncWith(IList<T> another, IEqualityComparer<T> comparer = null) could be an initial idea:

// Implementation
public static IList<TSource> SyncWith<TSource>(
    this IList<TSource> source,
    IList<TSource> another,
    IEqualityComparer<TSource> comparer = null)
{
  // Step 1: Remove redundant items, if any.
  source.Except(another).ToList().ForEach(x => _ = source.Remove(x));
  // Step 2: Update the list, index by index.
  for (var index = 0; index < another.Count; index++)
  {
    var newItem = another[index];
    if (index < source.Count)
    {
      var oldItem = source[index];
      if (comparer?.Equals(newItem, oldItem) ?? Equals(newItem, oldItem))
      {
        continue;
      }

      // Step 2-1: remove the old item, if it's been changed at the current position.
      source.RemoveAt(index);
    }

    // Step 2-2: insert the new item at the current position.
    source.Insert(index, newItem);
  }

  // Step 3: (last but not least) assertive verification.
  Debug.Assert(source.SequenceEqual(another, comparer));
  return source;
}

// Use Case
var data = await SomeDataService.FetchDataAsync(); // IList<TDataType>
var oc = new ObservableCollection<TDataType>(data);
MyListView.ItemSource = oc; // Initializes the entire list.

// When we need to update the entire list
data = await SomeDataService.FetchDataAsync(); // IList<TDataType>
_ = oc.SyncWith(data); // The UI won't blink and will apply the corresponding update animations.

Alternatives

No response

Additional info

No response

Help us help you

Yes, I'd like to be assigned to work on this item.

jingkecn avatar Oct 14 '23 15:10 jingkecn

Hello, 'jingkecn! Thanks for submitting a new feature request. I've automatically added a vote 👍 reaction to help get things started. Other community members can vote to help us prioritize this feature in the future!

ghost avatar Oct 14 '23 15:10 ghost

@Sergio0694 this sounds similar to an example you did in the MVVM Toolkit Sample App, eh?

Moving this to the dotnet repo as a feature request as it deals with IList as the interface vs. anything to a specific framework.

michael-hawker avatar Oct 16 '23 22:10 michael-hawker

@michael-hawker indeed, I did it with IList to make it as common as possible, thanks for moving it to the dotnet repo.

jingkecn avatar Oct 17 '23 13:10 jingkecn

@michael-hawker Any updates? ☺️

jingkecn avatar Oct 26 '23 14:10 jingkecn