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,多次复用。

希望大家多支持,以后会多放一些有意思的开箱即用的代码上来。

posted on 2013-12-09 23:05  ProJKY  阅读(2020)  评论(3编辑  收藏  举报