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

按列名对 IEnumerableT 进行排序

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.33/5 (2投票s)

2009年4月10日

CPOL

3分钟阅读

viewsIcon

44452

downloadIcon

223

OrderBy 的扩展方法重载,使用列名而不是 lambda。

引言

深入讨论表达式树和扩展方法超出了本文的范围。有很多资源可以学习这些东西。您可能应该先去看看。现在,继续...

显然不止一种方法可以解决问题。我相信这也不例外,但这是一种非常有趣的方法。这是 IEnumerable<T>.OrderBy() 扩展方法重载的实现。

背景

我记不清为什么了,但有一天,我的 UI 开发人员来找我说了一些这样的话,“我需要在 List 上运行 OrderBy,但是我不能使用 lambda 获取列名,因为在运行时我不知道它,我拥有的只是名称的字符串”。我立即想到重载 OrderBy(),使其接受一个字符串,该字符串表示要排序的列表中项目的属性的名称,而不是接受 lambda 的现有方法。当我开始考虑如何实现它时,我想到了获取字符串名称并以某种方式生成 lambda 的想法,然后我可以用来推迟到原始的 OrderBy()。这似乎是可能的事情,而且听起来比编写排序代码更有趣。开发人员多久才能从相同的旧 <在此处插入典型的后台系统功能> 中得到休息?我希望你喜欢它。我当然很喜欢写它。

使用代码

每个使用 LINQ 的人都可能熟悉 lambda 表达式,并且很可能现在正在使用它们,或者已经在 OrderBy() 中使用过它们。例如,您可能会看到按价格对产品列表进行排序,如下所示

var sortedProducts = myProductsList.OrderBy(p => p.Price);

我的 UI 大师想要的是

var sortedProducts = myProductsList.OrderBy("Price");

而且,这正是我给他的。显然,我也实现了 OrderByDescending()

var sortedProducts = myProductsList.OrderByDescending("Price");

关注点

这是 OrderBy() 扩展方法的签名。

public static IOrderedEnumerable<TSource> OrderBy<TSource, TKey>(
    this IEnumerable<TSource> source,
    Func<TSource, TKey> keySelector)

与任何扩展方法一样,第一个参数表示将在其上调用该方法的对象(this)。它在这里对我们没有特别的兴趣。第二个参数是 lambda 表达式,表示要按哪一列排序。这就是我们要用包含列名的字符串来代替的家伙。所以,我们想要

"Price"

而不是

p => p.Price

前两个感兴趣的行是

ParameterExpression list = Expression.Parameter(typeof(T), "list");
MemberExpression property = Expression.Property(list, columnName);

第一个创建了一个名为 "list" 的 ParameterExpression (PE),因为缺少更有趣的名称,它基于我们拥有的 IEnumerable 的类型 T。第二个在我们的 PE 上创建了一个 MemberExpression (ME),以设置对 "Price" 属性的访问(在我们的示例中;你的将是你想要排序的任何属性)。接下来,我们进行一些反射。

MethodInfo expressionMaker = me.GetMethod("MakeExpression", 
           BindingFlags.Static | BindingFlags.NonPublic);
MethodInfo expressionMethod = 
           expressionMaker.MakeGenericMethod(typeof(T), property.Type);

源代码中包含一个名为 MakeExpression 的方法。我们在第一次调用中反射到包含的类中,并获得一个指向它的 MethodInfo。在第二次调用中,由于 MakeExpression 是一个泛型方法,我们必须解析泛型。接下来,我们实际调用我们配置的方法

var lambda = expressionMethod.Invoke(null, new object[] { property, list });

此时,我们实际上有一个看起来像 OrderBy() 实际期望的 lambda 表达式;即

list => list.Price

我们通过如下编译将其转换为可运行的代码

var func = (lambda as LambdaExpression).Compile();

现在,进行更多反射以获得我们要调用的特定 Orderby() 的句柄。我在这里使用了一点 LINQ 来增加一些趣味性。我不相信这是最好的/最安全的方法。

var sortMaker = 
      (from s in enumerable.GetMethods()
       where
          (s.Name == ((IsAscending) ? ASCENDING : DESCENDING))
          && (s.GetParameters().Length == 2) //need a more reliable way to do this
      select s).First();

和以前一样,我们的方法“句柄”用于泛型方法,因此我们首先需要告诉它泛型将被解析为什么,然后再调用它。

MethodInfo sorter = sortMaker.MakeGenericMethod(typeof(T), property.Type);

现在,我们使用我们杜撰的 lambda 表达式来推迟到它

 return sorter.Invoke(source, new object[] { source, func }) 
               as IOrderedEnumerable<T>;

不要忘记引用您放置此类的命名空间 (using foo;),以便扩展方法能够显示出来。

附注:微软有一个执行此操作的库,称为 Dynamic API,我保证在执行此操作 6 个月后才知道它 :D

历史

感谢 Joe 指出并迫使我加强解释。

© . All rights reserved.