參考《Effective C#》Item 24「優先聲明式編程」,希望為類提供多屬性默認排序,且優先調用屬性實現的 IComparable<T>,回退至非泛型 IComparable。通過泛型實現類型安全,一次編寫、多處復用。
IComparer/IComparer<T>,按屬性序列比較[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false)]
public class DefaultSortAttribute : Attribute {
public string[] PropNames { get; set; }
public DefaultSortAttribute(string propName) { PropNames = new[] { propName }; }
public DefaultSortAttribute(string[] propNames) { PropNames = propNames; }
}
[DefaultSort(new[] { "ID", "Name" })]
public class SortData {
public int ID { get; set; }
public string Name { get; set; }
public string Value { get; set; }
public override string ToString() => $"ID:{ID},Name:{Name},Value:{Value}";
}
public class DefaultSortComparer: IComparer, IComparer { private readonly PropertyDescriptor[] _sortProps; private readonly bool _reverse; private readonly bool _isValueType; public DefaultSortComparer(bool reverse = false) { _reverse = reverse; var t = typeof(T); _isValueType = t.IsValueType; var attrs = t.GetCustomAttributes(typeof(DefaultSortAttribute), false); if (attrs.Length != 1) throw new NotSupportedException(t.Name); var propNames = ((DefaultSortAttribute)attrs[0]).PropNames; var props = TypeDescriptor.GetProperties(t); _sortProps = propNames.Select(n => props.Find(n, true)).ToArray(); } public int Compare(T x, T y) { // null 檢查、值類型處理略 foreach (var prop in _sortProps) { var xVal = prop.GetValue(x); var yVal = prop.GetValue(y); // null 比較略 var propType = xVal.GetType(); // 優先嘗試 IComparable var genComparable = typeof(IComparable<>).MakeGenericType(propType); if (genComparable.IsAssignableFrom(propType)) { var m = propType.GetMethod("CompareTo", new[] { propType }); var res = (int)m.Invoke(xVal, new[] { yVal }); if (res != 0) return _reverse ? -res : res; continue; } // 回退非泛型 IComparable if (xVal is IComparable cmp) { var res = cmp.CompareTo(yVal); if (res != 0) return _reverse ? -res : res; } else throw new ArgumentException($"Property {prop.Name} not comparable"); } return 0; } // 非泛型 IComparer.Compare 實現略 }
var list = new List{ new SortData { ID = 1, Name = "info", Value = "key" }, new SortData { ID = 3, Name = "64File", Value = "license" }, new SortData { ID = 2, Name = "cloneToken", Value = "comparer" }, new SortData { ID = 1, Name = "0est", Value = "backend" } }; list.Sort(new DefaultSortComparer (false)); list.ForEach(Console.WriteLine);
輸出先按 ID 升序,ID 相同再按 Name 升序。
IComparable<T> 避免裝箱/拆箱