首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > .NET > C# >

【原创+散分】逐步为对象集合构建一个通用的按指定属性排序的方法解决办法

2012-01-18 
【原创+散分】逐步为对象集合构建一个通用的按指定属性排序的方法有时候我们需要对集合中的自定义对象进行排

【原创+散分】逐步为对象集合构建一个通用的按指定属性排序的方法
有时候我们需要对集合中的自定义对象进行排序,以最原始的 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 + " 不存在。");    }}

排序时,可以这样调用:


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

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

调用时需要指定泛型参数:
C# code
Array.Sort(people, CompareByProperty<Person>("Name"));

到这里通用性已经很不错了,能否再更进一步呢?下面就是这篇文章所要抵达的终点:为 System.Array 类增加一个通用的按元素对象属性排序的方法,.NET 3.5 中新增了扩展方法,可以在不修改原有类代码的前提下为类增加新的实例方法,这正是我们这里所需要的,这需要新增加一个静态类,完整的代码如下:
C# code
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> 方法。
现在按属性排序只需这样调用:
C# code
people.SortBy("Birthday");

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


[解决办法]
SF 学习学习
[解决办法]
学习了,谢谢
------解决方案--------------------


支持原创,加精之
[解决办法]
学习了,3q
[解决办法]
恩 学习!
[解决办法]
留名,再细看,学习
[解决办法]
SF 学习学习
[解决办法]
学习 学习
[解决办法]
留名,学习~
[解决办法]
学习了
[解决办法]
楼主写得好详细,来学习下
[解决办法]

探讨
引用:
支持原创,加精之

哈,加精了,谢谢版主!
没有人给点意见吗?关于这篇文章中不妥当的地方。

[解决办法]
请问下这是几点几的?
[解决办法]
学习一下!
[解决办法]
Mark
[解决办法]
Mark
[解决办法]
先看下
[解决办法]
我在你的基础上修改了一下,我没有写成扩展方法,我还是写在一个类的静态方法里,并且加入排序的顺序:
C# code
    static class ExtensionArray    {        public enum Order        {            Ascending, Descending        }        public static void SortBy<T>(T[] array, string propName, Order order)        {            PropertyInfo pi = typeof(T).GetProperty(propName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);            if (pi == null)            {                throw new Exception("不存在名为" + propName + "的属性");            }            else if (Array.IndexOf(pi.PropertyType.GetInterfaces(), typeof(IComparable)) < 0)            {                throw new Exception("属性" + propName + "没有实现IComparable接口");            }            Array.Sort<T>(array, new Comparison<T>((t1, t2) =>            {                var v1 = (IComparable)pi.GetValue(t1, null);                var v2 = (IComparable)pi.GetValue(t2, null);                return order == Order.Ascending ? v1.CompareTo(v2) : v2.CompareTo(v1);            }));        }    }    class Program    {        static void Main(string[] args)        {            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))            };            ExtensionArray.SortBy<Person>(people, "Birthday", ExtensionArray.Order.Descending);            foreach (Person p in people)                Console.WriteLine(p.ToString());        }        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);            }        }    } 


[解决办法]
学习
[解决办法]
[code=C#][/code]
[解决办法]
学习……
[解决办法]
好好学习
[解决办法]
学习!!
[解决办法]
支持原创,学习~
顶起来!
[解决办法]
参考,学习。!
[解决办法]
OK,CTRL+c,收藏了
[解决办法]
学习下
[解决办法]
学习过,谢谢哈
[解决办法]
很好,顶了
[解决办法]
学习贴!
[解决办法]
学习,学习!
[解决办法]
留待后看!
[解决办法]

C# code
  [Serializable()]  public class SortableBindingList<T> : BindingList<T>, ITypedList, IBindingListView  {
[解决办法]
学习
[解决办法]
学习
[解决办法]

[解决办法]
好,支持
[解决办法]
向楼主学习!
[解决办法]
不懂,支持
[解决办法]
接分吧…
[解决办法]
DDDD!!!
[解决办法]
DDDD!!!
[解决办法]
值得学习!
[解决办法]
顶起来。。。
[解决办法]
学习
[解决办法]
学习 学习
[解决办法]
学习了~~
[解决办法]
关注
[解决办法]
关注ing。。。
[解决办法]
学习了,谢谢
[解决办法]
收藏
[解决办法]
看看,学习了,谢谢LZ
------解决方案--------------------


学习学习。
[解决办法]
学习学习。
[解决办法]
留名,再细看,学习

[解决办法]

[解决办法]
学习 学习 在学习
[解决办法]
mark
[解决办法]
顶楼主的分享精神
[解决办法]
向楼主学习
[解决办法]
学习了
[解决办法]
不错的好帖,学习+收藏
[解决办法]
MArk
[解决办法]
学习
[解决办法]
学习
[解决办法]
学习了,谢谢
[解决办法]
学习学习
[解决办法]
楼的说得好,对于你个人来说,这样的收获会获益一辈子, 
正所谓失败是成功之母,失败只不过是你成功的奠基石,LZ单枪匹马的干已经 
很令人钦佩了
[解决办法]

[解决办法]
up
[解决办法]
支持
[解决办法]
mark
[解决办法]
收藏一个。
[解决办法]
UP一下,接分了。。。。。。
[解决办法]
帮顶,分析的不错
[解决办法]


支持原创。。。 支持散分。。。
[解决办法]
mark
[解决办法]
好好好
[解决办法]
学习,再学习!
[解决办法]
支持原创,学习!
up
[解决办法]
写的真好,学习中。
[解决办法]
支持一下
[解决办法]
不错,我很喜欢这样的氛围!
[解决办法]
学习。
[解决办法]
学习了,接分
[解决办法]
学习
我一般用arraylist de sort()方法
[解决办法]
学习
------解决方案--------------------


学习过。谢谢楼主。
[解决办法]
只能支持,并收藏了。
[解决办法]
还以为是java,哈哈
[解决办法]
学习,JF
[解决办法]
学习中
[解决办法]
好帖
[解决办法]
太好了。
[解决办法]
学习
[解决办法]
up

热点排行