Welcome

首页 / 软件开发 / C# / 使用Attribute+Reflection来提高代码重用

使用Attribute+Reflection来提高代码重用2014-04-08 cnblogs ProJKY这篇文章两个目的,一是开阔设计的思路,二是实例代码可以拿来就用。

设计的思路来源于《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;}}}