Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ private void Setup()
// right list
AdvancedCollectionView acv = new(EmployeeCollection);
acv.Filter = x => !int.TryParse(((Employee)x).Name, out _);
acv.SortDescriptions.Add(new(nameof(Employee.Name), SortDirection.Ascending));
acv.SortDescriptions.Add(new SortDescription<Employee>(nameof(Employee.Name), SortDirection.Ascending));

CollectionView = acv;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,6 @@ namespace CommunityToolkit.WinUI.Collections;
/// <summary>
/// A collection view implementation that supports filtering, sorting and incremental loading
/// </summary>
#if NET8_0_OR_GREATER
[System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Item sorting uses reflection to get property types and may not be AOT compatible.")]
#endif
public partial class AdvancedCollectionView : IAdvancedCollectionView, INotifyPropertyChanged, ISupportIncrementalLoading, IComparer<object>
{
private readonly List<object> _view;
Expand Down Expand Up @@ -383,7 +380,7 @@ public Predicate<object> Filter
int IComparer<object>.Compare(object x, object y)
#pragma warning restore CA1033 // Interface methods should be callable by child types
{
if (!_sortProperties.Any())
if (_sortProperties.Count == 0)
{
var listType = _source?.GetType();
Type type;
Expand All @@ -401,7 +398,7 @@ int IComparer<object>.Compare(object x, object y)
{
if (!string.IsNullOrEmpty(sd.PropertyName))
{
_sortProperties[sd.PropertyName] = type.GetProperty(sd.PropertyName);
_sortProperties[sd.PropertyName] = sd.GetProperty(type);
}
}
}
Expand All @@ -419,8 +416,8 @@ int IComparer<object>.Compare(object x, object y)
{
var pi = _sortProperties[sd.PropertyName];

cx = pi.GetValue(x!);
cy = pi.GetValue(y!);
cx = pi.GetValue(x);
cy = pi.GetValue(y);
}

var cmp = sd.Comparer.Compare(cx, cy);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.

using System.Collections;
using System.Diagnostics.CodeAnalysis;

namespace CommunityToolkit.WinUI.Collections;

Expand All @@ -14,7 +15,7 @@ public class SortDescription
/// <summary>
/// Gets the name of property to sort on
/// </summary>
public string PropertyName { get; }
public virtual string? PropertyName { get; }

/// <summary>
/// Gets the direction of sort
Expand All @@ -33,8 +34,10 @@ public class SortDescription
/// <param name="direction">Direction of sort</param>
/// <param name="comparer">Comparer to use. If null, will use default comparer</param>
public SortDescription(SortDirection direction, IComparer? comparer = null)
: this(null!, direction, comparer!)
{
PropertyName = null;
Direction = direction;
Comparer = comparer ?? ObjectComparer.Instance;
}

/// <summary>
Expand All @@ -43,13 +46,22 @@ public SortDescription(SortDirection direction, IComparer? comparer = null)
/// <param name="propertyName">Name of property to sort on</param>
/// <param name="direction">Direction of sort</param>
/// <param name="comparer">Comparer to use. If null, will use default comparer</param>
#if NET5_0_OR_GREATER
[RequiresUnreferencedCode("Item sorting with the property name uses reflection to get the property and is not trim-safe. Either use SortDescription<T> to preserve the required metadata, or use the other constructor without a property name.")]
#endif
public SortDescription(string propertyName, SortDirection direction, IComparer? comparer = null)
{
PropertyName = propertyName;
Direction = direction;
Comparer = comparer ?? ObjectComparer.Instance;
}


[UnconditionalSuppressMessage("Trimming", "IL2070:'this' argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to target method. The parameter of method does not have matching annotations.",
Justification = "The path which does reflection is only triggered if the user uses the constructor with RequiresUnreferencedCode (which bubbles the warning to them)")]
internal virtual PropertyInfo? GetProperty(Type type)
=> PropertyName != null ? type.GetProperty(PropertyName) : null;

private class ObjectComparer : IComparer
{
public static readonly IComparer Instance = new ObjectComparer();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Collections;
using System.Diagnostics.CodeAnalysis;

namespace CommunityToolkit.WinUI.Collections;

/// <summary>
/// A generic version of <see cref="SortDescription"/> which preserves the required metadata for reflection-based sorting.
/// </summary>
/// <typeparam name="T">The type to sort</typeparam>
public class SortDescription<
#if NET5_0_OR_GREATER
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
#endif
T> : SortDescription
{
private readonly PropertyInfo _prop;

/// <inheritdoc/>
public override string PropertyName => _prop.Name;

/// <summary>
/// Initializes a new instance of the <see cref="SortDescription{T}"/> class.
/// </summary>
/// <param name="propertyName">Name of property to sort on</param>
/// <param name="direction">Direction of sort</param>
/// <param name="comparer">Comparer to use. If null, will use default comparer</param>
public SortDescription(string propertyName, SortDirection direction, IComparer? comparer = null) : base(direction, comparer)
{
_prop = typeof(T).GetProperty(propertyName) ?? throw new ArgumentException("Type does not have the expected property");
}

internal override PropertyInfo? GetProperty(Type type) =>
type.IsAssignableTo(_prop.DeclaringType) ? _prop : throw new ArgumentException("This SortDescription is not compatible with this type");
}