這篇文章兩個目的,一是開闊設計的思路,二是實例代碼可以拿來就用。
設計的思路來源於《Effective c#》第一版Item 24: 優先使用聲明式編程而不是命令式編程。特別的地方是,希望提供多個屬性的默認排序,而不僅僅只根據一個屬性,另外一點是,優先調用對象屬性實現了的IComparable<T>接口,如果沒有實現接口,才調用IComparable進行比較。排序類實現泛型,得到類型安全。
總的思路:Attribute用來裝飾我們想要獲取元數據的類,使用Reflection來提取元數據,根據提取到的元數據實現一些和對象無關的組件功能。
那么,這個例子要實現的效果是用Attribute裝飾類對象,設置該對象的默認排序屬性,排序的時候,根據這些默認排序來進行排序。
1 [DefaultSort(new string[] {"ID", "Name"})] 2 class SortData 3 { 4 public int ID { get; set; } 5 6 public string Name { get; set; } 7 8 public string Value { get; set; } 9 10 public override string ToString() 11 { 12 return String.Format("ID:{0},Name:{1},Value:{2}", ID, Name, Value); 13 } 14 }
對於SortData對象來說,我們希望根據它的ID來排序,如果ID相等,再根據Name屬性來排序。像它的名字暗示的一樣,這是默認的行為,不需要我們實現SortData的IComparable<SortData>接口,將來要改變排序規則,只要修改DefaultSort中屬性名稱數組的內容就夠了,很方便。
原書中記錄的DefaultAttribute只能根據一個屬性名稱來排序,不夠實用,希望它像下面的類一樣,能記錄多個屬性的名稱。
1 [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple=false)] 2 public class DefaultSortAttribute : System.Attribute 3 { 4 private string[] m_propNames; 5 6 public string[] PropNames 7 { 8 get { return m_propNames; } 9 set { m_propNames = value; } 10 } 11 12 public DefaultSortAttribute(string propName) 13 { 14 m_propNames = new string[] { propName }; 15 } 16 17 public DefaultSortAttribute(string[] propNames) 18 { 19 m_propNames = propNames; 20 } 21 }
注意仍然保留了只希望拿一個屬性來排序的構造函數,對類進行裝飾時,往類上面放[DefaultSort(new string[] {"ID", "Name"})] 和[DefaultSort("ID")]類似的聲明就夠了。
既然使用Attribute裝飾了類,就要知道這樣的元數據,下面需要采用Reflection讀到要排序的默認屬性名,相對於原書中的改進是,使用泛型和優先使用屬性的IComparable<T>接口來比較排序。
1 using System; 2 using System.Linq; 3 using System.Collections; 4 using System.Collections.Generic; 5 using System.ComponentModel; 6 using System.Reflection; 7 8 namespace ProJKY.Extensions 9 { 10 public class DefaultSortComparer<T> : IComparer, IComparer<T> 11 { 12 private readonly PropertyDescriptor[] m_sortProps; 13 private readonly bool m_reverse = false; 14 private readonly bool m_valueType = false; 15 16 public DefaultSortComparer() : 17 this(false) 18 { } 19 20 public DefaultSortComparer(bool reverse) 21 { 22 m_reverse = reverse; 23 Type t = typeof(T); 24 m_valueType = t.IsValueType; 25 26 object[] a = t.GetCustomAttributes(typeof(DefaultSortAttribute), false); 27 28 // 強制檢查,不支持沒有用DefaultSortAttribute裝飾的類 29 if (a.Length != 1) 30 throw new NotSupportedException(t.Name); 31 32 DefaultSortAttribute sortName = a[0] as DefaultSortAttribute; 33 34 string[] propNames = sortName.PropNames; 35 36 m_sortProps = new PropertyDescriptor[propNames.Length]; 37 38 PropertyDescriptorCollection props = TypeDescriptor.GetProperties(t); 39 40 for (int i = 0; i < propNames.Length; i++){ 41 foreach (PropertyDescriptor p in props){ 42 if (p.Name == propNames[i]){ 43 m_sortProps[i] = p; 44 break; 45 } 46 } 47 } 48 } 49 50 int IComparer.Compare(object left, object right) 51 { 52 if (HasNull(left, right) == true) 53 { 54 int nullCompare = CompareWithNull(left, right); 55 56 return m_reverse ? -nullCompare : nullCompare; 57 } 58 59 if (left.GetType() != right.GetType()) 60 throw new ArgumentException("left and right not match."); 61 62 if (typeof(T).IsAssignableFrom(left.GetType()) == false) 63 throw new ArgumentException("type not compatible."); 64 65 return Compare((T)left, (T)right); 66 } 67 68 public int Compare(T x, T y) 69 { 70 if (m_valueType == false && HasNull(x, y) == true){ 71 int nullCompare = CompareWithNull(x, y); 72 return m_reverse ? -nullCompare : nullCompare; 73 } 74 75 foreach (var prop in m_sortProps){ 76 object xValue = prop.GetValue(x); 77 object yValue = prop.GetValue(y); 78 79 if (HasNull(xValue, yValue) == true){ 80 int nullCompare = CompareWithNull(xValue, yValue); 81 82 return m_reverse ? -nullCompare : nullCompare; 83 } 84 85 Type propType = xValue.GetType(); 86 87 // 優先使用IComaprable<T>接口 88 if (typeof(IComparable<>).MakeGenericType(propType).IsAssignableFrom(propType)) 89 { 90 MethodInfo methodInfo = propType.GetMethods().FirstOrDefault(method => method.Name == "CompareTo" 91 && method.GetParameters().Length == 1 92 && method.GetParameters()[0].ParameterType == propType); 93 94 int gretValue = (int)methodInfo.Invoke(xValue, new object[] { yValue }); 95 96 if (gretValue == 0) continue; 97 98 return m_reverse ? -gretValue : gretValue; 99 } 100 101 IComparable xNonGeneric = xValue as IComparable; 102 IComparable yNonGeneric = yValue as IComparable; 103 104 if (xNonGeneric == null) 105 throw new ArgumentException("Property " + prop.Name + " is not comparable."); 106 107 int retValue = xNonGeneric.CompareTo(yValue); 108 109 if (retValue == 0) continue; 110 111 return m_reverse ? -retValue : retValue; 112 } 113 114 return 0; 115 } 116 117 int CompareWithNull(object left, object right) 118 { 119 if ((left == null) && (right == null)) 120 return 0; 121 122 if (left == null) 123 return -1; 124 125 return 1; 126 } 127 128 bool HasNull(object left, object right) 129 { 130 if (left == null || right == null) 131 return true; 132 return false; 133 } 134 } 135 }
需要注意的是DefaultSortComparer<T>是泛型的,並實現了IComparer, IComparer<T>接口,實現了這兩個接口,才方便排序。
代碼寫貼了這么多,用起來怎么用呢。
1 var data1 = new SortData() { ID = 1, Name = "info", Value = "key"}; 2 var data2 = new SortData() { ID = 3, Name = "64File", Value = "license" }; 3 var data3 = new SortData() { ID = 2, Name = "cloneToken", Value = "comparer" }; 4 var data4 = new SortData() { ID = 1, Name = "0est", Value = "backend" }; 5 6 List<SortData> sortData = new List<SortData>(); 7 sortData.Add(data1); 8 sortData.Add(data2); 9 sortData.Add(data3); 10 sortData.Add(data4); 11 12 sortData.Sort(new DefaultSortComparer<SortData>(false)); 13 14 sortData.ForEach(data => Console.WriteLine(data));
結果就不獻丑了,經測試,能正常工作。
通過這個例子,就可以看到,要實現它的關鍵是,Attribute負責裝飾類,Reflection負責讀取特定Attribute裝飾后的元數據信息,實現和特定類類型無關的組件。一次Coding,多次復用。
希望大家多支持,以后會多放一些有意思的開箱即用的代碼上來。