Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using IncrementalLoadingSource in AdvancedCollectionView Causes NullReferenceException when adding a SortDescription #642

Open
4 of 24 tasks
k-g-nolan opened this issue Feb 24, 2025 · 1 comment · May be fixed by #643
Open
4 of 24 tasks

Comments

@k-g-nolan
Copy link

k-g-nolan commented Feb 24, 2025

Describe the bug

When adding a SortDescription to an AdvancedCollectionView that has an IncrementalLoadingCollection as its source, a null reference exception is generated:

System.InvalidOperationException
  HResult=0x80131509
  Message=Failed to compare two elements in the array.
  Source=System.Private.CoreLib
  StackTrace:
   at System.ThrowHelper.ThrowInvalidOperationException(ExceptionResource resource, Exception e)
   at System.Collections.Generic.ArraySortHelper`1.Sort(Span`1 keys, IComparer`1 comparer)
   at System.Array.Sort[T](T[] array, Int32 index, Int32 length, IComparer`1 comparer)
   at System.Collections.Generic.List`1.Sort(Int32 index, Int32 count, IComparer`1 comparer)
   at CommunityToolkit.WinUI.Collections.AdvancedCollectionView.HandleSortChanged()
   at CommunityToolkit.WinUI.Collections.AdvancedCollectionView.SortDescriptions_CollectionChanged(Object sender, NotifyCollectionChangedEventArgs e)
   at System.Collections.ObjectModel.ObservableCollection`1.OnCollectionChanged(NotifyCollectionChangedEventArgs e)
   at ESx.ViewModels.PlcLogViewModel.<>c__DisplayClass41_0.<.ctor>b__0(Task _) in C:\Users\knolan\source\repos\ESx\ESx\ViewModels\PlcLogViewModel.cs:line 189
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)

  This exception was originally thrown at this call stack:
    CommunityToolkit.WinUI.Collections.AdvancedCollectionView.System.Collections.Generic.IComparer<object>.Compare(object, object)
    System.Collections.Generic.ArraySortHelper<T>.SwapIfGreater(System.Span<T>, System.Comparison<T>, int, int)
    System.Collections.Generic.ArraySortHelper<T>.PickPivotAndPartition(System.Span<T>, System.Comparison<T>)
    System.Collections.Generic.ArraySortHelper<T>.IntroSort(System.Span<T>, int, System.Comparison<T>)
    System.Collections.Generic.ArraySortHelper<T>.IntrospectiveSort(System.Span<T>, System.Comparison<T>)
    System.Collections.Generic.ArraySortHelper<T>.Sort(System.Span<T>, System.Collections.Generic.IComparer<T>)

Inner Exception 1:
NullReferenceException: Object reference not set to an instance of an object.

Steps to reproduce

WinUI CommunityToolkit 8.1 running on Windows 11 Pro 22631.4890, compiled for .net8

1. Create an `IncrementalLoadingCollection` for a user-defined class.
2. Call `RefreshAsync()` to load items into the collection.
3. Follow that call by creating an `AdvancedCollectionView`, using the `IncrementalLoadingCollection` as its source.
4. Add a sort description to the `AdvancedCollectionView`. This will generate a null reference exception.

Expected behavior

AdvancedCollectionView should sort the items in the list based on the given SortDescription.

Screenshots

Image

Code Platform

  • UWP
  • WinAppSDK / WinUI 3
  • Web Assembly (WASM)
  • Android
  • iOS
  • MacOS
  • Linux / GTK

Windows Build Number

  • Windows 10 1809 (Build 17763)
  • Windows 10 1903 (Build 18362)
  • Windows 10 1909 (Build 18363)
  • Windows 10 2004 (Build 19041)
  • Windows 10 20H2 (Build 19042)
  • Windows 10 21H1 (Build 19043)
  • Windows 10 21H2 (Build 19044)
  • Windows 10 22H2 (Build 19045)
  • Windows 11 21H2 (Build 22000)
  • Other (specify)

Other Windows Build number

Windows 11 23H2 (Build 22631.4890)

App minimum and target SDK version

  • Windows 10, version 1809 (Build 17763)
  • Windows 10, version 1903 (Build 18362)
  • Windows 10, version 1909 (Build 18363)
  • Windows 10, version 2004 (Build 19041)
  • Windows 10, version 2104 (Build 20348)
  • Windows 11, version 22H2 (Build 22000)
  • Other (specify)

Other SDK version

Windows 11, build 22621

Visual Studio Version

2022

Visual Studio Build Number

17.12.3

Device form factor

Desktop

Additional context

Here is the code in question:

// "Log" is a user-defined class with DateTime property "Timestamp"
var collection = new IncrementalLoadingCollection<LogSource, Log>(new LogSource(_logManager), PAGE_SIZE);

_ = collection.RefreshAsync()
          .ContinueWith(_ => {
              CurrentLogEntries = new AdvancedCollectionView(collection, true);
              CurrentLogEntries.SortDescriptions.Add(new SortDescription(nameof(Log.Timestamp), SortDirection.Descending));  // error happens here.
          }, TaskContinuationOptions.ExecuteSynchronously);  // Work to initialize the ACV is done on the UI thread.

I checked the code for the AdvancedCollectionView, and I believe this is happening because of how the ACV handles generic types. It assumes the object type is whatever the first generic argument is:

// In AdvancedCollectionView.cs:


#pragma warning disable CA1033 // Interface methods should be callable by child types
    int IComparer<object>.Compare(object x, object y)
#pragma warning restore CA1033 // Interface methods should be callable by child types
    {
        if (!_sortProperties.Any())
        {
            var listType = _source?.GetType();
            Type type;

            if (listType != null && listType.IsGenericType)
            {
                /* For IncrementalLoadingCollection, the first generic argument is NOT the type of the objects in the list*/
                type = listType.GetGenericArguments()[0];  // type is some IIncrementalLoadingSource
            }
            else
            {
                type = x.GetType();
            }

            foreach (var sd in _sortDescriptions)
            {
                if (!string.IsNullOrEmpty(sd.PropertyName))
                {
                    _sortProperties[sd.PropertyName] = type.GetProperty(sd.PropertyName);  // GetProperty() likely returns null here because type is not the correct type.
                }
            }
        }

        foreach (var sd in _sortDescriptions)
        {
            object cx, cy;

            if (string.IsNullOrEmpty(sd.PropertyName))
            {
                cx = x;
                cy = y;
            }
            else
            {
                var pi = _sortProperties[sd.PropertyName];  // pi is null here

                cx = pi.GetValue(x!);  // Likely NullReferenceException thrown here
                cy = pi.GetValue(y!);
            }

            var cmp = sd.Comparer.Compare(cx, cy);

            if (cmp != 0)
            {
                return sd.Direction == SortDirection.Ascending ? +cmp : -cmp;
            }
        }

        return 0;
    }

I was able to work around it by creating a wrapper for IncrementalLoadingCollection that switches the type arguments:

private sealed class IncrementalLoadingWrapper<T, TSource> : IncrementalLoadingCollection<TSource, T> where TSource : IIncrementalSource<T>
{
    public IncrementalLoadingWrapper(TSource source, int pageSize) : base(source, pageSize) {}
}

Help us help you

Yes, but only if others can assist.

@k-g-nolan
Copy link
Author

Created PR #643 to fix this issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
1 participant