From 4e19ed8f24ae98338f67167eb6bccffd2385e98a Mon Sep 17 00:00:00 2001 From: Dameng <313880747@qq.com> Date: Mon, 14 Oct 2024 23:57:59 +0800 Subject: [PATCH] Fix DataGrid native aot crash when sorting. (#17248) * Fix DataGrid native aot crash when sorting. * Update DataGridSortDescription.cs * add customType test * does not crash when property type is not sortable. * check `RuntimeFeature.IsDynamicCodeSupported` to fallback to reflection implemention --- .../Collections/DataGridSortDescription.cs | 27 ++- .../Collections/ComparerTests.cs | 223 ++++++++++++++++++ 2 files changed, 249 insertions(+), 1 deletion(-) create mode 100644 tests/Avalonia.Controls.DataGrid.UnitTests/Collections/ComparerTests.cs diff --git a/src/Avalonia.Controls.DataGrid/Collections/DataGridSortDescription.cs b/src/Avalonia.Controls.DataGrid/Collections/DataGridSortDescription.cs index ab73ed73c7..6503e9e825 100644 --- a/src/Avalonia.Controls.DataGrid/Collections/DataGridSortDescription.cs +++ b/src/Avalonia.Controls.DataGrid/Collections/DataGridSortDescription.cs @@ -170,8 +170,33 @@ namespace Avalonia.Collections if (type == typeof(string)) return _cultureSensitiveComparer.Value; else - return (typeof(Comparer<>).MakeGenericType(type).GetProperty("Default")).GetValue(null, null) as IComparer; + return GetComparerForNotStringType(type); } + + internal static IComparer GetComparerForNotStringType(Type type) + { +#if NET6_0_OR_GREATER + if(System.Runtime.CompilerServices.RuntimeFeature.IsDynamicCodeSupported == false) + { + if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>) && type.GetGenericArguments()[0].IsAssignableTo(typeof(IComparable))) + return Comparer.Create((x, y) => + { + if (x == null) + return y == null ? 0 : -1; + else + return (x as IComparable)!.CompareTo(y); + }); + else if (type.IsAssignableTo(typeof(IComparable))) //enum should be here + return Comparer.Create((x, y) => (x as IComparable)!.CompareTo(y)); + else + return Comparer.Create((x, y) => 0); //avoid using reflection to avoid crash on AOT + } + else +#endif + return (typeof(Comparer<>).MakeGenericType(type).GetProperty("Default")).GetValue(null, null) as IComparer; + + } + private Type GetPropertyType(object o) { return o.GetType().GetNestedPropertyType(_propertyPath); diff --git a/tests/Avalonia.Controls.DataGrid.UnitTests/Collections/ComparerTests.cs b/tests/Avalonia.Controls.DataGrid.UnitTests/Collections/ComparerTests.cs new file mode 100644 index 0000000000..55e144aa1d --- /dev/null +++ b/tests/Avalonia.Controls.DataGrid.UnitTests/Collections/ComparerTests.cs @@ -0,0 +1,223 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using Avalonia.Collections; +using Avalonia.Logging; +using Xunit; + +namespace Avalonia.Controls.DataGridTests.Collections; + +public class NoStringTypeComparerTests +{ + public static IEnumerable GetComparerForNotStringTypeParameters() + { + yield return + [ + nameof(Item.IntProp1), + (object item, object value) => + { + (item as Item)!.IntProp1 = (int)value; + }, + (object item) => (object)(item as Item)!.IntProp1, + new object[] { 2, 3, 1 }, + new object[] { 1, 2, 3 } + ]; + yield return + [ + nameof(Item.IntProp2), + (object item, object value) => + { + (item as Item)!.IntProp2 = (int?)value; + }, + (object item) => (object)(item as Item)!.IntProp2, + new object[] { 2, 3, null, 1 }, + new object[] { null, 1, 2, 3 } + ]; + yield return + [ + nameof(Item.DoubleProp1), + (object item, object value) => + { + (item as Item)!.DoubleProp1 = (double)value; + }, + (object item) => (object)(item as Item)!.DoubleProp1, + new object[] { 2.1, 3.1, 1.1 }, + new object[] { 1.1, 2.1, 3.1 } + ]; + yield return + [ + nameof(Item.DoubleProp2), + (object item, object value) => + { + (item as Item)!.DoubleProp2 = (double?)value; + }, + (object item) => (object)(item as Item)!.DoubleProp2, + new object[] { 2.1, 3.1, null, 1.1 }, + new object[] { null, 1.1, 2.1, 3.1 } + ]; + yield return + [ + nameof(Item.DecimalProp1), + (object item, object value) => + { + (item as Item)!.DecimalProp1 = (decimal)value; + }, + (object item) => (object)(item as Item)!.DecimalProp1, + new object[] { 2.1M, 3.1M, 1.1M }, + new object[] { 1.1M, 2.1M, 3.1M } + ]; + yield return + [ + nameof(Item.DecimalProp2), + (object item, object value) => + { + (item as Item)!.DecimalProp2 = (decimal?)value; + }, + (object item) => (object)(item as Item)!.DecimalProp2, + new object[] { 2.1M, 3.1M, null, 1.1M }, + new object[] { null, 1.1M, 2.1M, 3.1M } + ]; + yield return + [ + nameof(Item.EnumProp1), + (object item, object value) => + { + (item as Item)!.EnumProp1 = (LogEventLevel)value; + }, + (object item) => (object)(item as Item)!.EnumProp1, + new object[] { LogEventLevel.Information, LogEventLevel.Debug, LogEventLevel.Error }, + new object[] { LogEventLevel.Debug, LogEventLevel.Information, LogEventLevel.Error } + ]; + yield return + [ + nameof(Item.EnumProp2), + (object item, object value) => + { + (item as Item)!.EnumProp2 = (LogEventLevel?)value; + }, + (object item) => (object)(item as Item)!.EnumProp2, + new object[] + { + LogEventLevel.Information, + LogEventLevel.Debug, + null, + LogEventLevel.Error + }, + new object[] + { + null, + LogEventLevel.Debug, + LogEventLevel.Information, + LogEventLevel.Error + } + ]; + yield return + [ + nameof(Item.CustomProp2), + (object item, object value) => + { + (item as Item)!.CustomProp2 = (CustomType?)value; + }, + (object item) => (object)(item as Item)!.CustomProp2, + new object[] + { + new CustomType() { Prop = 2 }, + new CustomType() { Prop = 3 }, + null, + new CustomType() { Prop = 1 } + }, + new object[] + { + null, + new CustomType() { Prop = 1 }, + new CustomType() { Prop = 2 }, + new CustomType() { Prop = 3 } + } + ]; + } + + [Theory] + [MemberData(nameof(GetComparerForNotStringTypeParameters))] + public void GetComparerForNotStringType_Correctly_WhenSorting( + string pathName, + Action setAction, + Func getAction, + object[] orignal, + object[] ordered + ) + { + List items = new(); + for (int i = 0; i < orignal.Length; i++) + { + var item = new Item(); + setAction(item, orignal[i]); + items.Add(item); + } + + //Ascending + var sortDescription = DataGridSortDescription.FromPath( + pathName, + ListSortDirection.Ascending + ); + sortDescription.Initialize(typeof(Item)); + var result = sortDescription.OrderBy(items).ToList(); + + for (int i = 0; i < ordered.Length; i++) + { + Assert.Equal(ordered[i], getAction(result[i])); + } + + //Descending + sortDescription = DataGridSortDescription.FromPath(pathName, ListSortDirection.Descending); + sortDescription.Initialize(typeof(Item)); + result = sortDescription.OrderBy(items).ToList(); + + ordered = ordered.Reverse().ToArray(); + for (int i = 0; i < ordered.Length; i++) + { + Assert.Equal(ordered[i], getAction(result[i])); + } + } + + private class Item + { + public int IntProp1 { get; set; } + public int? IntProp2 { get; set; } + public double DoubleProp1 { get; set; } + public double? DoubleProp2 { get; set; } + + public decimal DecimalProp1 { get; set; } + public decimal? DecimalProp2 { get; set; } + + public LogEventLevel EnumProp1 { get; set; } + public LogEventLevel? EnumProp2 { get; set; } + public CustomType? CustomProp2 { get; set; } + } + + public struct CustomType : IComparable + { + public int Prop { get; set; } + + public int CompareTo(object obj) + { + if (obj is CustomType other) + { + return Prop.CompareTo(other.Prop); + } + else + { + return 1; + } + } + + public override bool Equals(object obj) + { + if (obj is CustomType other) + { + return Prop == other.Prop; + } + return false; + } + } +}