AvaloniaUI / Avalonia

Develop Desktop, Embedded, Mobile and WebAssembly apps with C# and XAML. The most popular .NET UI client technology

Home Page:https://avaloniaui.net

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Consider reviewing IAvaloniaList.TrackItemPropertyChanged-extension

AliveDevil opened this issue · comments

Is your feature request related to a problem? Please describe.

Currently IAvaloniaList.TrackItemPropertyChanged-extension allocates a Tuple-object instance for each property changed event.
This can be made alloc-free by switching to ValueTuple.

Additionally the TrackItemPropertyChanged-method doesn't support resets, which should be reevaluated, as the tracking list can be used to dispose of all event registrations.

The Disposable returned by ForEachItem in TrackItemPropertyChanged is never explicitely disposed - which can result in never-collected and always firing NotifyPropertyChanged-handlers.

Describe the solution you'd like

API Proposal:

  namespace Avalonia.Collections;

  public static class AvaloniaListExtensions
  {
-     public static IDisposable TrackItemPropertyChanged<T>(
-         this IAvaloniaReadOnlyList<T> collection,
-         Action<Tuple<object?, PropertyChangedEventArgs>> callback);
+     public static IDisposable TrackItemPropertyChanged<T>(
+         this IAvaloniaReadOnlyList<T> collection,
+         Action<(object? Sender, PropertyChangedEventArgs Args)> callback);
}

Functionality change:

  public static IDisposable TrackItemPropertyChanged<T>(
              this IAvaloniaReadOnlyList<T> collection,
              Action<Tuple<object?, PropertyChangedEventArgs>> callback)
  {
      …
-     collection.ForEachItem(
+     var collectionSubscription = collection.ForEachItem(
      …
      return Disposable.Create(() =>
      {
+         collectionSubscription.Dispose();
          …
      };
}

Resetable TrackItemPropertyChanged:

- () => throw new NotSupportedException("Collection reset not supported."));
+ () =>
+ {
+     tracked.ForEach(x => x.PropertyChanged -= handler);
+     tracked.Clear();
+ }

Alternatively this could use PooledList<T> to create a copy, which is then disposed:

() =>
{
    using PooledList<INotifyPropertyChanged> copy = new(tracked);
    tracked.Clear();
    copy.ForEach(x => x.PropertyChanged -= handler);
}

Describe alternatives you've considered

I considered creating the functionality myself in an extension method, unfortunately Avalonia.Reactive.Observable and PooledList are internal.

Open for discussion is whether TrackItemPropertyChanged should be able to filter on AvaloniaPropertyChangedEventArgs and AvaloniaPropertyChangedEventArgs<T>.

Additional context

I'm open to providing a PR.