您的位置:首页 > 编程语言

Attribute+Reflection,提高代码重用

2013-12-09 23:05 197 查看
这篇文章两个目的,一是开阔设计的思路,二是实例代码可以拿来就用。

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


需要注意的是DefaultSortComparer<T>是泛型的,并实现了IComparer, IComparer<T>接口,实现了这两个接口,才方便排序。

代码写贴了这么多,用起来怎么用呢。

var data1 = new SortData() { ID = 1, Name = "info", Value = "key"};
var data2 = new SortData() { ID = 3, Name = "64File", Value = "license" };
var data3 = new SortData() { ID = 2, Name = "cloneToken", Value = "comparer" };
var data4 = new SortData() { ID = 1, Name = "0est", Value = "backend" };

List<SortData> sortData = new List<SortData>();
sortData.Add(data1);
sortData.Add(data2);
sortData.Add(data3);
sortData.Add(data4);

sortData.Sort(new DefaultSortComparer<SortData>(false));

sortData.ForEach(data => Console.WriteLine(data));


结果就不献丑了,经测试,能正常工作。

通过这个例子,就可以看到,要实现它的关键是,Attribute负责装饰类,Reflection负责读取特定Attribute装饰后的元数据信息,实现和特定类类型无关的组件。一次Coding,多次复用。

希望大家多支持,以后会多放一些有意思的开箱即用的代码上来。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: