Attribute+Reflection,提高代碼重用


這篇文章兩個目的,一是開闊設計的思路,二是實例代碼可以拿來就用。

設計的思路來源於《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,多次復用。

希望大家多支持,以后會多放一些有意思的開箱即用的代碼上來。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM