65.9K
CodeProject 正在变化。 阅读更多。
Home

动态列表排序

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.78/5 (51投票s)

2005年12月19日

CPOL

6分钟阅读

viewsIcon

329274

downloadIcon

1773

使用动态方法和委托动态排序列表。

引言

这篇文章源于丹麦网站 DotNetForum.dk 上的一篇帖子。发帖人询问如何像 SQL 那样动态地排序一组(例如)person 对象。当时,有人提到了 IComparerIComparer<> 接口,并认为可能可以使用这些接口创建某种通用比较器。还提供了一个 Peter Provost 的文章链接,题为“Generic Property Comparer Class”。这篇文章解释了如何在 .NET 1.1 中使用 IComparer 创建通用比较器。考虑到 .NET 1.1,Peter 的解决方案还可以,但由于几个原因,在 .NET 2.0 上下文中相当糟糕。

  1. 它一次只能排序一个属性。
  2. 它只能按升序排序。
  3. 它不是强类型的。
  4. 它非常慢!

注意:第 3 点和第 4 点只是 .NET 1.1 缺乏泛型和动态方法的事实造成的。

因此,我决定尝试利用 .NET 2.0 中的新泛型以及框架的其他一些非常酷的功能,来提出一个更好的解决方案。

在开始这样的项目时,一个关键点是要牢记性能。我的意思是,没有人愿意等待 5 秒钟才能对列表进行排序。这个项目的另一个关键点是它应该是动态的,并且如果可能的话,应该允许用户输入一个类似 SQL 的“Order By”语法的字符串,例如“FirstName, Age DESC, LastName”。

解决方案

我匆忙完成的第一个解决方案是一个只使用泛型且能够对任意数量的属性进行升序和降序排序的比较器。因此,该解决方案满足了大多数重要要求。然而,尽管我在看到 Peter 的 1.1 解决方案之前就完成了它,但实际上它们非常相似,只是我的解决方案使用泛型来指定对象的类型,而在 Peter 的解决方案中,您将项目类型作为参数传入。不幸的是,该解决方案也存在性能问题。经过几次性能测试,我清楚地认识到这个解决方案是不可行的。

问题在于,如果没有动态生成一定量的特定代码,您就无法真正满足要求。至少,这是我一段时间后的结论。幸运的是,.NET 2.0 有一个名为“动态方法”的新功能。

“动态方法”是在运行时在内存中创建的方法。我不会深入探讨这个主题,但我建议您去了解一下。基本上,我所做的是在内存中编译“Compare”方法的强类型版本,并使用委托调用它。正如您将在下一节中看到的,这是一种在保持方法完全动态且非常快速的同时,获得强类型方法的一种非常强大的方式。

性能比较

我进行了几次性能测试,将最常用的排序方法与 Peter Provost 的解决方案进行了比较。结果如下:

Diagram of performance test results

图 1:10 到 100000 个项目的排序时间。

正如您在图 1 中看到的,动态比较器解决方案是排序自定义实体列表的好方法。动态比较器与硬编码比较器并列第一。硬编码比较器似乎起步较慢,但这只是测量误差。这清楚地表明,从运行时的角度来看,用于排序的动态方法与硬编码方法几乎相同,只是我们通过委托调用它。

第二名是弱类型和强类型数据集。它们都遵循相同的趋势,即斜率略陡于其他方法。这意味着与其它方法相比,它们的性能下降速度更快。有些人可能会认为强类型数据集的排序速度应该比弱类型数据集快,但请再想想……强类型数据集只是弱类型数据集的一个重载版本,所以实际上它使用的是与弱类型版本相同的排序方法。还值得一提的是,填充包含 10000 个或更多项目的列表比填充常规列表要慢得多。

最后,第三名是通用类比较器。正如您所看到的,它比其他排序方法慢得多。这并非因为它设计或编码不当,而是因为 .NET 1.1 不支持泛型也不支持动态方法。

用法

那么如何使用这个比较器呢?很简单,就像 1-2-3 一样。

//Example:
string orderBy = "FirstName, LastName DESC"; // 1
DynamicComparer<Person> comparer = new DynamicComparer<Person>(orderBy); // 2
persons.Sort(comparer.Compare); // 3
  1. 声明一个排序字符串或一个 SortProperty 数组。注意:这可以由自定义编辑网格创建和维护。
  2. 使用通用构造函数声明 DynamicComparer 类的一个实例。
  3. List<Person> 上调用 sort 方法。

就是这样!

那么幕后发生了什么?让我们从前面示例的第 2 行开始。在实例化 DynamicComparer 时,该类将首先解析输入字符串到 SortProperty 数组。然后,这些 SortProperties 将根据我们在实例化 DynamicComparer 时指定的类型(在本例中为“Person”)进行验证。此验证检查 Person 类是否为 public,是否具有任何名为“FirstName”和“LastName”的公共属性,这些属性是否可读,以及最后,这些属性的类型是否实现了 IComparable 接口。如果任何验证失败,该类将抛出异常。如果验证成功,该类将使用指定的 SortPropertyies 生成一个动态方法,并实例化一个指向该方法的内部委托。然后,当调用 DynamicComparer 上的“Compare”方法时,它将能够调用此委托。这意味着比较器已准备好使用。

注意:如果您想更改 DynamicComparer 实例的排序方式,可以调用实例上的“Initialize”方法,并传入一个新的 ORDER BY 字符串或 SortProperty 数组。

下一步,我们在“persons”列表上调用“Sort”方法,并传入对 comparer.Compare 方法的引用。

结论

动态比较器似乎是一种非常高效的方式,可以在保持速度的同时获得灵活的比较器,而且我还没有看到任何更好甚至不同的解决方案具有相同的灵活性和性能。尽管如此,比较器在某些方面仍可以改进,但主要是在实例化过程中。我将新的建议和想法留给您来改进比较器。我期待您的反馈!

© . All rights reserved.