Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a breaking change technically


/// <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;
}


Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: extra blank line

Suggested change

[UnconditionalSuppressMessage("Trimming", "IL2070:'this' argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to target method. The parameter of method does not have matching annotations.",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not safe. Someone could derive from SortDescriptor, use the base constructor with no annotations, manually assign or override PropertyName to return a property, and then this would silently suppress a valid warning.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I went with this design to avoid adding a suppression in SortDescription<T>, but you're right that this opens up unsafe extensibility since the classes aren't sealed. Should I just go for the suppression in SortDescription<T>?

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");
}