這篇文章兩個目的,一是開闊設計的思路,二是執行個體代碼可以拿來就用。
設計的思路來源於《Effective c#》第一版Item 24: 優先使用聲明式編程而不是命令式編程。特別 的地方是,希望提供多個屬性的預設排序,而不僅僅只根據一個屬性,另外一點是,優先調用對象屬性 實現了的IComparable<T>介面,如果沒有實現介面,才調用IComparable進行比較。排序類實現 泛型,得到型別安全。
總的思路:Attribute用來裝飾我們想要擷取中繼資料的類,使用Reflection來提取中繼資料,根據提取 到的中繼資料實現一些和對象無關的組件功能。
那麼,這個例子要實現的效果是用Attribute裝飾類對象,設定該對象的預設排序屬性,排序的時候 ,根據這些預設排序來進行排序。
[DefaultSort(new string[] {"ID", "Name"})]class SortData{ public int ID { get; set; } public string Name { get; set; } public string Value { get; set; } public override string ToString() { return String.Format("ID:{0},Name:{1},Value:{2}", ID, Name, Value); }}
對於SortData對象來說,我們希望根據它的ID來排序,如果ID相等,再根據Name屬性來排序。像它 的名字暗示的一樣,這是預設的行為,不需要我們實現SortData的IComparable<SortData>介面 ,將來要改變定序,只要修改DefaultSort中屬性名稱數組的內容就夠了,很方便。
原書中記錄的DefaultAttribute只能根據一個屬性名稱來排序,不夠實用,希望它像下面的類一樣 ,能記錄多個屬性的名稱。
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple=false)] public class DefaultSortAttribute : System.Attribute { private string[] m_propNames; public string[] PropNames { get { return m_propNames; } set { m_propNames = value; } } public DefaultSortAttribute(string propName) { m_propNames = new string[] { propName }; } public DefaultSortAttribute(string[] propNames) { m_propNames = propNames; } }
注意仍然保留了只希望拿一個屬性來排序的建構函式,對類進行裝飾時,往類上面放[DefaultSort (new string[] {"ID", "Name"})] 和[DefaultSort("ID")]類似的聲 明就夠了。
既然使用Attribute裝飾了類,就要知道這樣的中繼資料,下面需要採用Reflection讀到要排序的預設 屬性名稱,相對於原書中的改進是,使用泛型和優先使用屬性的IComparable<T>介面來比較排序。
using System;using System.Linq;using System.Collections;using System.Collections.Generic;using System.ComponentModel;using System.Reflection; namespace ProJKY.Extensions{ public class DefaultSortComparer<T> : IComparer, IComparer<T> { private readonly PropertyDescriptor[] m_sortProps; private readonly bool m_reverse = false; private readonly bool m_valueType = false; public DefaultSortComparer() : this(false) { } public DefaultSortComparer(bool reverse) { m_reverse = reverse; Type t = typeof(T); m_valueType = t.IsValueType; object[] a = t.GetCustomAttributes(typeof(DefaultSortAttribute), false); // 強制檢查,不支援沒有用DefaultSortAttribute裝飾的類 if (a.Length != 1) throw new NotSupportedException(t.Name); DefaultSortAttribute sortName = a[0] as DefaultSortAttribute; string[] propNames = sortName.PropNames; m_sortProps = new PropertyDescriptor[propNames.Length]; PropertyDescriptorCollection props = TypeDescriptor.GetProperties(t); for (int i = 0; i < propNames.Length; i++){ foreach (PropertyDescriptor p in props){ if (p.Name == propNames[i]){ m_sortProps[i] = p; break; } } } } int IComparer.Compare(object left, object right) { if (HasNull(left, right) == true) { int nullCompare = CompareWithNull(left, right); return m_reverse ? -nullCompare : nullCompare; } if (left.GetType() != right.GetType()) throw new ArgumentException("left and right not match."); if (typeof(T).IsAssignableFrom(left.GetType()) == false) throw new ArgumentException("type not compatible."); return Compare((T)left, (T)right); } public int Compare(T x, T y) { if (m_valueType == false && HasNull(x, y) == true){ int nullCompare = CompareWithNull(x, y); return m_reverse ? -nullCompare : nullCompare; } foreach (var prop in m_sortProps){ object xValue = prop.GetValue(x); object yValue = prop.GetValue(y); if (HasNull(xValue, yValue) == true){ int nullCompare = CompareWithNull(xValue, yValue); return m_reverse ? -nullCompare : nullCompare; } Type propType = xValue.GetType(); // 優先使用IComaprable<T>介面 if (typeof(IComparable<>).MakeGenericType(propType).IsAssignableFrom(propType)) { MethodInfo methodInfo = propType.GetMethods().FirstOrDefault(method => method.Name == "CompareTo" && method.GetParameters().Length == 1 && method.GetParameters()[0].ParameterType == propType); int gretValue = (int)methodInfo.Invoke(xValue, new object[] { yValue }); if (gretValue == 0) continue; return m_reverse ? -gretValue : gretValue; } IComparable xNonGeneric = xValue as IComparable; IComparable yNonGeneric = yValue as IComparable; if (xNonGeneric == null) throw new ArgumentException("Property " + prop.Name + " is not comparable."); int retValue = xNonGeneric.CompareTo(yValue); if (retValue == 0) continue; return m_reverse ? -retValue : retValue; } return 0; } int CompareWithNull(object left, object right) { if ((left == null) && (right == null)) return 0; if (left == null) return -1; return 1; } bool HasNull(object left, object right) { if (left == null || right == null) return true; return false; } }}