您的位置:首页 > 其它

逐步为对象集合构建一个通用的按指定属性排序的方法

2009-04-06 18:31 666 查看
有时候我们需要对集合中的自定义对象进行排序,以最原始的 System.Array 为例,如

Person[] people = new Person[]{
new Person(3, "Andy", new DateTime(1982, 10, 3)),
new Person(1, "Tom", new DateTime(1993, 2, 10)),
new Person(2, "Jerry", new DateTime(1988, 4, 23))
};


类 Person 的定义为:

class Person
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime Birthday { get; set; }
public Person(int id, string name, DateTime birthday)
{
Id = id;
Name = name;
Birthday = birthday;
}
public override string ToString()
{
return String.Format("Id: {0,-6}Name: {1,-20}Birthday: {2:yyyy-MM-dd}", Id, Name, Birthday);
}
}


可能会需要根据 Id、Name 及 Birthday 进行排序。在 .NET 中,自定义对象数组排序最常见的实现方式是在对象中实现 IComparable 接口,然后调用 Array.Sort(array) 静态方法,显然,在上述情形下,这不是一个好的解决办法。其实 .NET 提供了一个泛型的 Sort() 静态方法,可以根据指定的谓词函数进行排序,其定义如下:

public static void Sort<T>(
T[] array,
Comparison<T> comparison
)


按照定义,我们先定义一个谓词函数:

static int CompareById(Person first, Person second)
{
if (first.Id > second.Id)
return 1;
if (first.Id == second.Id)
return 0;
return -1;
}


然后在排序时,如下调用:

Array.Sort(people, new Comparison<Person>(CompareById));


使用语句输出结果:

foreach (Person p in people)
Console.WriteLine(p);


可以看到 Person 数组已经按照 Id 排序了。因为 .NET 内置的类型大多都实现了 IComparable 接口,包括值类型,所以上面的谓词函数可以简化为:

static int CompareById(Person first, Person second)
{
return first.Id.CompareTo(second.Id);
}


虽然这个函数写起来很简单,但是对每个需要进行排序的属性都写一个函数,也挺麻烦,幸好 .NET 2.0 提供了匿名委托,不用再单独定义函数了:

Array.Sort(people, delegate(Person first, Person second){
return first.Id.CompareTo(second.Id);
});


简单了许多,如果是 .NET 3.5,可以用 Lamda 表达式进一步简化:

Array.Sort(people, (first, second) => first.Id.CompareTo(second.Id));


在实际应用开发中,从性能和易用性上来说,到这一步大多数情形下已经足够。下面的部分可能有过度设计的嫌疑,但这里主要是研究 .NET 一些特性的使用,所以我们继续往下。

能否直接返回一个委托,使我们不必关心 Person 类的具体属性比较,而直接根据属性进行排序呢?答案是肯定的。为 Person 类添加一个静态方法:

public static Comparison<Person> CompareByProperty(string name)
{
switch (name)
{
case "Id":
return (first, second) => first.Id.CompareTo(second.Id);
case "Name":
return (first, second) => first.Name.CompareTo(second.Name);
case "Birthday":
return (first, second) => first.Birthday.CompareTo(second.Birthday);
default:
throw new Exception("属性 " + name + " 不存在。");
}
}


排序时,可以这样调用:

Array.Sort(people, Person.CompareByProperty("Birthday"));


还行,但是如果为 Person 类增加了新的属性,如果要按照新属性排序,必须要修改代码,能不能做到增加新属性而不修改代码呢?当然可以。因为要用到反射,为简化代码,突出主题,我们假定所有使用到的属性都实现了 IComparable 接口,修改上面的 CompareByProperty(string) 方法为:

public static Comparison<Person> CompareByProperty(string name)
{
Type typeOfPerson = typeof(Person);
PropertyInfo p = typeOfPerson.GetProperty(name);
if (p == null)
throw new Exception("属性 " + name + " 不存在。");
// 假定该类所有的属性均实现了接口 IComparable
return (first, second) => ((IComparable)p.GetValue(first, null)).CompareTo(p.GetValue(second, null));
}


因为方法的签名仍保持一致,所以调用的语句不用修改。仔细观察上面的代码,应该可以把它的应用再扩大化,而不仅限于 Person 类,而这显然是泛型的长项。当然,这样的话,不应再把这个方法放在 Person 类中,我们暂时先把它移到主程序中,稍后再为它寻找一个好的归宿,修改后的 CompareByProperty 泛型方法代码如下

public static Comparison<T> CompareByProperty<T>(string name)
{
Type typeOfPerson = typeof(T);
PropertyInfo p = typeOfPerson.GetProperty(name);
if (p == null)
throw new Exception("属性 " + name + " 不存在。");
// 假定该类所有的属性均实现了接口 IComparable
return (first, second) => ((IComparable)p.GetValue(first, null)).CompareTo(p.GetValue(second, null));
}


调用时需要指定泛型参数:

Array.Sort(people, CompareByProperty<Person>("Name"));


到这里通用性已经很不错了,能否再更进一步呢?下面就是这篇文章所要抵达的终点:为 System.Array 类增加一个通用的按元素对象属性排序的方法,.NET 3.5 中新增了扩展方法,可以在不修改原有类代码的前提下为类增加新的实例方法,这正是我们这里所需要的,这需要新增加一个静态类,完整的代码如下:

static class ExtensionArray
{
public static void SortBy(this Array array, string name)
{
Type elementType = array.GetType().GetElementType();
Type bridge = typeof(Bridge<>).MakeGenericType(elementType);
MethodInfo sortMethod = bridge.GetMethod("Sort");
sortMethod.Invoke(null, new object[] { array, name });
}
private static class Bridge<T>
{
private static Comparison<T> CompareByProperty(string name) //不必再是泛型方法
{
Type typeOfPerson = typeof(T);
PropertyInfo p = typeOfPerson.GetProperty(name);
if (p == null)
throw new Exception("属性 " + name + " 不存在。");
// 假定该类所有的属性均实现了接口 IComparable
return (first, second) => ((IComparable)p.GetValue(first, null)).CompareTo(p.GetValue(second, null));
}
public static void Sort(Array array, string name)
{
Array.Sort((T[])array, CompareByProperty(name));
}
}
}


注意,在上面的代码中增加了一个私有的嵌套类 Bridge,这主要是为了便于调用 Array.Sort<T>() 泛型方法,如果没有这个类进行过渡,则必须使用大量的反射方法才能调用 Array.Sort<T> 方法。

现在按属性排序只需这样调用:

people.SortBy("Birthday");


代码很简单,但是我们应当看到,通用性的扩展是以牺牲性能为代价的。尤其是在后期引入反射以后,性能大幅下降,简单测试了一下,Array.Sort(people, (first, second) => first.Id.CompareTo(second.Id)) 与 people.SortBy("Id") 性能相差约为 120 倍。所以在实际应用中,我们应把握好度,适可而止。但是从学习的角度上来说,我觉得在自己能力范围内尽量深入,还是很有价值的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐