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

有时候我们需要对集合中的自定义对象进行排序,以最原始的 System.Array 为例,如

类 Person 的定义为:

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

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

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

使用语句输出结果:

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

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

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

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

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

排序时,可以这样调用:

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

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

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

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

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

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值