CommunityToolkit / CommunityToolkit/Windows

Using IncrementalLoadingSource in AdvancedCollectionView Causes NullReferenceException when adding a SortDescription

Aperta
#642 1 commento 0 reazioni 0 assegnatari Vedi su GitHub
bug components::collections
Lingua principale
C#
Stelle
1.1k
Fork
166
Metriche di merge delle PR
Nessuna PR unita negli ultimi 30g

Descrizione

### 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.Compare(object, object)
System.Collections.Generic.ArraySortHelper.SwapIfGreater(System.Span, System.Comparison, int, int)
System.Collections.Generic.ArraySortHelper.PickPivotAndPartition(System.Span, System.Comparison)
System.Collections.Generic.ArraySortHelper.IntroSort(System.Span, int, System.Comparison)
System.Collections.Generic.ArraySortHelper.IntrospectiveSort(System.Span, System.Comparison)
System.Collections.Generic.ArraySortHelper.Sort(System.Span, System.Collections.Generic.IComparer)

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

### Steps to reproduce

```text
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](https://github.com/user-attachments/assets/e24e5e3f-42a7-4223-b6eb-7f2d5ef4722e)

### Code Platform

- [ ] UWP
- [x] 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)
- [x] Other (specify)

### Other Windows Build number

Windows 11 23H2 (Build 22631.4890)

### App minimum and target SDK version

- [x] 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)
- [x] 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(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.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 : IncrementalLoadingCollection where TSource : IIncrementalSource
{
public IncrementalLoadingWrapper(TSource source, int pageSize) : base(source, pageSize) {}
}
```

### Help us help you

Yes, but only if others can assist.

Guida per i contributori

Apri la guida per i contributori

Direzione di ricerca

Start with AdvancedCollectionView.cs, especially the IComparer.Compare path shown in the issue, and reproduce the failure using IncrementalLoadingCollection, RefreshAsync(), and a SortDescription on Log.Timestamp. Trace how the collection's generic arguments are interpreted and verify that sorting the loaded items completes without the NullReferenceException.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Valutazione

Stack tecnologico
csharp
Ambito
tooling
Tipo di issue
Bug
Difficoltà
4/5
Tempo stimato
3-5 giorni
Stato di attività
Ferma
Chiarezza
Specificata chiaramente
Idoneità per principianti
35/100

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.