日期:2014-05-20  浏览次数:20719 次

【原创+散分】逐步为对象集合构建一个通用的按指定属性排序的方法
有时候我们需要对集合中的自定义对象进行排序,以最原始的 System.Array 为例,如
C# code
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 的定义为:
C# code
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() 静态方法,可以根据指定的谓词函数进行排序,其定义如下:
C# code
public static void Sort<T>(
    T[] array,
    Comparison<T> comparison
)

按照定义,我们先定义一个谓词函数:
C# code
static int CompareById(Person first, Person second)
{
    if (first.Id > second.Id)
        return 1;
    if (first.Id == second.Id)
        return 0;
    return -1;
}

然后在排序时,如下调用:
C# code
Array.Sort(people, new Comparison<Person>(CompareById));

使用语句输出结果:
C# code
foreach (Person p in people)
    Console.WriteLine(p);

可以看到 Person 数组已经按照 Id 排序了。因为 .NET 内置的类型大多都实现了 IComparable 接口,包括值类型,所以上面的谓词函数可以简化为:
C# code
static int CompareById(Person first, Person second)
{
    return first.Id.CompareTo(second.Id);
}

虽然这个函数写起来很简单,但是对每个需要进行排序的属性都写一个函数,也挺麻烦,幸好 .NET 2.0 提供了匿名委托,不用再单独定义函数了:
C# code
Array.Sort(people, delegate(Person first, Person second){
    return first.Id.CompareTo(second.Id);
});

简单了许多,如果是 .NET 3.5,可以用 Lamda 表达式进一步简化:
C# code
Array.Sort(people, (first, second) => first.Id.CompareTo(second.Id));

在实际应用开发中,从性能和易用性上来说,到这一步大多数情形下已经足够。下面的部分可能有过度设计的嫌疑,但这里主要是研究 .NET 一些特性的使用,所以我们继续往下。
能否直接返回一个委托,使我们不必关心 Person 类的具体属性比较,而直接根据属性进行排序呢?答案是肯定的。为 Person 类添加一个静态方法:
C# code
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 + " 不存在。");
    }
}

排序时,可以这样调用: