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

原文:http://topic.csdn.net/u/20090407/13/371533da-f709-4f1f-bda9-b4a18060e713.html?seed=924471686

有时候我们需要对集合中的自定义对象进行排序,以最原始的 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 () 泛型方法,如果没有这个类进行过渡,则必须使用大量的反射方法才能调用 Array.Sort 方法。
现在按属性排序只需这样调用:

people.SortBy( " Birthday " );
代码很简单,但是我们应当看到,通用性的扩展是以牺牲性能为代价的。尤其是在后期引入反射以后,性能大幅下降,简单测试了一下, Array.Sort(people, (first, second) => first.Id.CompareTo(second.Id)) 与 people.SortBy("Id") 性能相差约为 120 倍。所以在实际应用中,我们应把握好度,适可而止。但是从学习的角度上来说,我觉得在自己能力范围内尽量深入,还是很有价值的。

转载于:https://www.cnblogs.com/Yjianyong/archive/2011/12/28/2304541.html

你可能感兴趣的:((转)逐步为对象集合构建一个通用的按指定属性排序的方法)