Linq排序在一系列Linq操作中应该使用频率***的,关于Linq排序的文章也很多,但是笔者的这篇文章最值得一读了,因为他把理论与实践结合的十分***,理解起来也很简单,希望能给你带来帮助。
在程序开发中,对数据进行排序是很常见的操作。现在就来演示一下Linq排序,假设现在有一个类Customer,定义如下所示:
- public class Customer
- {
- public string Id { get; set; }
- public string Name { get; set; }
- public decimal Age { get; set; }
- }
我们现在要对很多Customer对象进行排序,最简单的就是使用Linq排序的orderby子句:
- from c in Customers orderby c.Id select c;
上面实现了按照Id来进行Linq排序。可是需求变了,用户现在想用Name来排序。好办!把上面的改一改比如下面这样就可以了:
- from c in Customers orderby c.Name select c;
可是需求又变了,用户现在说,你在程序中不能写死,得列一个菜单,我点哪个你就按哪个给我Linq排序。这个也不难办:
- var searchResult = from c in Customers select c;
- if (columnName == "Id")
- {
- searchResult = from c in Customers orderby c.Id select c;
- }
- else if (columnName == "Name")
- {
- searchResult = from c in Customers orderby c.Id select c;
- }
- else ...
这确实解决了问题,可是这样的代码不易维护。如果加属性了怎么办?如果属性改名字了怎么办?如果有好多的不同的类都需要这样的Linq排序怎么办?
下面我介绍一种较为通用的解决方案,此方案的核心技术是反射(Reflection)和扩展方法(Extension Methods)。
- public static IOrderedQueryable
OrderBy ( - this IQueryable
source, - Expression
> keySelector, - IComparer
comparer - )
参数source是一个用来排序的对象,keySelector是用来取出用来Linq排序的键的函数,comparer(比较器)用来比较取出的两个键值。
对象的属性类型可能多种多样,而我们又不想为每种类型分别指定comparer(因为那样做的话也将是一堆if else…)。变通一下思路,不管遇到哪种类型的属性,我们都先把它的值放到一个共同的容器中,然后为这个容器写一个comparer类。我们把类型判断留到了这个comparer中,因为类型是有限的,至少我们需要处理的那些属性的类型是有限的。
上面提到的这个值的容器也是一个类,定义如下:
- public class CommonComparableValue
- {
- public object RealValue { get; set; }
- }
相应的比较类定义如下:
- public class CommonComparableValueComparer : IComparer
- {
- public int Compare(CommonComparableValue x, CommonComparableValue y)
- {
- string s = x.RealValue as string;
- if (s != null)
- {
- return s.CompareTo(y.RealValue);
- }
- int? i = x.RealValue as int?;
- if (i != null)
- {
- return i.Value - (int)y.RealValue;
- }
- decimal? d = x.RealValue as decimal?;
- if (d != null)
- {
- return d.Value.CompareTo((decimal)y.RealValue);
- }
- throw new NotImplementedException("NotImplemented Data Type!!!");
- }
- }
这里的比较类只用到了int,string等几种类型,如果属性有其它的类型,也应该在这里添加。从代码实现可以看出,即使属性的类型是复杂数据类型也可以这么处理。
现在来看keySelector的实现。它是用来取出待比较的属性值的函数。这个函数应该是这个样子的:
- public delegate TResult Func
- ( T arg )
在这里,T的类型就是Customer,TResult就是刚刚已经那个存放任意属性类型的值的容器CommonComparableValue。
在这个实现取键值的函数里,我们只有一个Customer类型的参数arg可用,而那个用来Linq排序的属性名字是在运行期间确定的,如何才能取出我们想要的属性的值呢?方法是这样的,先通过扩展方法为Customer加一个名为GetSortingKeyValue的取键值方法,代码如下:
- public static class CustomerSortExtension
- {
- public static CommonComparableValue GetSortingKeyValue(this Customer ainfo, string columnName)
- {
- Type t = ainfo.GetType();
- PropertyInfo pinfo = t.GetProperty(columnName);
- if (pinfo == null)
- {
- throw new Exception("Property " + columnName + "not found");
- }
- else
- {
- return new CommonComparableValue
- {
- RealValue = pinfo.GetValue(ainfo, null)
- };
- }
- }
- }
这里就是通过一个字符串获取属性值,核心是反射。接下来只要在那个keySelector方法中调用GetSortingKeyValue方法就可以了。
到这里,各种准备活动就做完了。现在来看一下怎么把这些东西组织起来实现Linq排序:
- var searchResult = from c in Customers select c;
- Func
myFunc = x => x.GetSortingKeyValue(columnName); - CommonComparableValueComparer comparer = new CommonComparableValueComparer();
- searchResult = searchResult.OrderBy(myFunc, comparer);
这种方案的主要内容到这里就介绍完了。
***提一下,如果你想把这些东西用到你的代码中,你一般需要做的只有:将扩展方法的***个参数改为你需要Linq排序的那个类型。比如要为Person排序,扩展方法则可以是这个样子:
- public static CommonComparableValue GetSortingKeyValue(this Customer ainfo, string columnName)
当然,那个CommonComparableValueComparer类也应该根据实际类型修改以支持更多的属性类型。
以上就是对Linq排序的详细介绍,从理论到方法,很有价值的一篇文章呦!
【编辑推荐】