diff --git a/components/Collections/samples/AdvancedCollectionViewSample.xaml.cs b/components/Collections/samples/AdvancedCollectionViewSample.xaml.cs index 8dfb75e6..932ea5b7 100644 --- a/components/Collections/samples/AdvancedCollectionViewSample.xaml.cs +++ b/components/Collections/samples/AdvancedCollectionViewSample.xaml.cs @@ -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(nameof(Employee.Name), SortDirection.Ascending)); CollectionView = acv; } diff --git a/components/Collections/src/AdvancedCollectionView/AdvancedCollectionView.cs b/components/Collections/src/AdvancedCollectionView/AdvancedCollectionView.cs index 416d87f8..b889c35c 100644 --- a/components/Collections/src/AdvancedCollectionView/AdvancedCollectionView.cs +++ b/components/Collections/src/AdvancedCollectionView/AdvancedCollectionView.cs @@ -13,9 +13,6 @@ namespace CommunityToolkit.WinUI.Collections; /// /// A collection view implementation that supports filtering, sorting and incremental loading /// -#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 { private readonly List _view; @@ -383,7 +380,7 @@ public Predicate Filter int IComparer.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; @@ -401,7 +398,7 @@ int IComparer.Compare(object x, object y) { if (!string.IsNullOrEmpty(sd.PropertyName)) { - _sortProperties[sd.PropertyName] = type.GetProperty(sd.PropertyName); + _sortProperties[sd.PropertyName] = sd.GetProperty(type); } } } @@ -419,8 +416,8 @@ int IComparer.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); diff --git a/components/Collections/src/AdvancedCollectionView/SortDescription.cs b/components/Collections/src/AdvancedCollectionView/SortDescription.cs index abc6b83a..18e0bb46 100644 --- a/components/Collections/src/AdvancedCollectionView/SortDescription.cs +++ b/components/Collections/src/AdvancedCollectionView/SortDescription.cs @@ -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; @@ -14,7 +15,7 @@ public class SortDescription /// /// Gets the name of property to sort on /// - public string PropertyName { get; } + public virtual string? PropertyName { get; } /// /// Gets the direction of sort @@ -33,8 +34,10 @@ public class SortDescription /// Direction of sort /// Comparer to use. If null, will use default comparer public SortDescription(SortDirection direction, IComparer? comparer = null) - : this(null!, direction, comparer!) { + PropertyName = null; + Direction = direction; + Comparer = comparer ?? ObjectComparer.Instance; } /// @@ -43,6 +46,9 @@ public SortDescription(SortDirection direction, IComparer? comparer = null) /// Name of property to sort on /// Direction of sort /// Comparer to use. If null, will use default comparer +#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 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; @@ -50,6 +56,12 @@ public SortDescription(string propertyName, SortDirection direction, IComparer? 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(); diff --git a/components/Collections/src/AdvancedCollectionView/SortDescription{T}.cs b/components/Collections/src/AdvancedCollectionView/SortDescription{T}.cs new file mode 100644 index 00000000..ea65f3d4 --- /dev/null +++ b/components/Collections/src/AdvancedCollectionView/SortDescription{T}.cs @@ -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; + +/// +/// A generic version of which preserves the required metadata for reflection-based sorting. +/// +/// The type to sort +public class SortDescription< +#if NET5_0_OR_GREATER + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] +#endif + T> : SortDescription +{ + private readonly PropertyInfo _prop; + + /// + public override string PropertyName => _prop.Name; + + /// + /// Initializes a new instance of the class. + /// + /// Name of property to sort on + /// Direction of sort + /// Comparer to use. If null, will use default comparer + 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"); +}