C#中给继承自IEnumerable的对象(最熟知的就是List了)提供了很丰富的扩展方法,涉及列表操作的方方面面。而扩展方法ThenBy就是很有意思的一个,它的实现也很巧妙。

如果有这样的一个Team类,里面有三个属性。

Team.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Team
{
public Team (string name, int timeCost, int score)
{
this.Name = name;
this.TimeCost = timeCost;
this.Score = score;
} public string Name {
get;
private set;
} public int TimeCost {
get;
private set;
} public int Score {
get;
private set;
} }

然后我们有一个Team的List。

1
2
3
4
5
List<Team> teams = new List<Team> ();
teams.Add (new Team ("teamA", 10, 22));
teams.Add (new Team ("teamB", 12, 20)); teams.Add (new Team ("teamC", 8, 18));

那么如何求出teams中得分最高的那个队伍那?这个很简单,只需要一句话即可。

1
2
var result = teams.OrderByDescending (team => team.Score).First ();
Console.WriteLine (result.Name); // teamA

由于List实现了IEnumerable接口,而System.Linq中的Enumerable类中有针对IEnumerable接口的名为OrderByDescending的扩展方法,所以我们直接调用这个扩展方法可以对List按照指定的key进行降序排列,再调用First这个扩展方法来获取列表中的第一个元素。

如果我的List变成这个样子。

1
2
3
4
List<Team> teams = new List<Team> ();
teams.Add (new Team ("teamA", 10, 18));
teams.Add (new Team ("teamB", 12, 16));
teams.Add (new Team ("teamC", 8, 18));

由于有可能两组以上的队伍都可能拿到最高分,那么在这些最高分的队伍中,我们选取用时最少的作为最终优胜者。有人说那可以这样写。

1
var result = teams.OrderByDescending (team => team.Score).OrderBy(team => team.TimeCost).First ();

先对列表按Score降序排列,再对列表按TimeCost升序排列,然后取结果中的第一个元素。看来貌似是正确的,但其实是错误的。因为第一次调用OrderByDescending方法后返回了一个排序后的数组,再调用OrderBy是另外一次排序了,它会丢弃上一次排序,这与我们定的先看积分,如果积分相同再看耗时的规则违背。

那么应该如何实现那?C#给我们提供了一个叫做ThenBy的方法,可以满足我们的要求。

1
2
3
var result = teams.OrderByDescending (team => team.Score).ThenBy(team => team.TimeCost).First ();

Console.WriteLine (result.Name); // teamC

新的问题又来了。第一次调用OrderByDescending方法时返回的是一个新对象,再对这个新对象调用ThenBy时,它只有记录了上一次排序规则,才能达到我们想要的效果。那么C#是如何记录上次排序使用的key那?

这就先要看OrderByDescending方法是如何实现了的。查看源码发现OrderByDescending有两个重载,实现如下。

1
2
3
4
5
6
7
8
9
10
11
public static IOrderedEnumerable<TSource> OrderByDescending<TSource, TKey> (this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
{
return source.OrderByDescending (keySelector, null);
} public static IOrderedEnumerable<TSource> OrderByDescending<TSource, TKey> (this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IComparer<TKey> comparer)
{
Check.SourceAndKeySelector (source, keySelector);
return new OrderedSequence<TSource, TKey> (source, keySelector, comparer, SortDirection.Descending); }

在第二个重载中我们看到OrderByDescending方法返回时的是一个继承了IOrderedEnumerable接口的对象OrderedSequence。这个对象记录了我们的排序规则。

而我们再查看下ThenBy方法的定义。

1
2
3
4
5
6
7
8
9
10
public static IOrderedEnumerable<TSource> ThenBy<TSource, TKey> (this IOrderedEnumerable<TSource> source, Func<TSource, TKey> keySelector, IComparer<TKey> comparer)
{
Check.SourceAndKeySelector (source, keySelector);
return source.CreateOrderedEnumerable<TKey> (keySelector, comparer, false);
} public static IOrderedEnumerable<TSource> ThenBy<TSource, TKey> (this IOrderedEnumerable<TSource> source, Func<TSource, TKey> keySelector)
{
return source.ThenBy (keySelector, null);
}

我们可以看到ThenBy这个扩展方法追加到的对象类型要实现IOrderedEnumerable接口,而OrderBy方法恰好返回的就是这个类型接口对象。那我们再看看IOrderedEnumerable接口的定义。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
using System;
using System.Collections;
using System.Collections.Generic; namespace System.Linq
{
public interface IOrderedEnumerable<TElement> : IEnumerable<TElement>, IEnumerable
{
//
// Methods
//
IOrderedEnumerable<TElement> CreateOrderedEnumerable<TKey> (Func<TElement, TKey> keySelector, IComparer<TKey> comparer, bool descending);
} }

其继承自IEnumerable接口,并且要实现一个名为CreateOrderedEnumerable的方法,正是ThenBy方法实现中调用的这个方法。

所以玄机在OrderedSequence这个类上。实现了IEnumerable接口对象调用OrderBy后会返回OrderedSequence这个对象。而该对象记录了当前排序的规则,其实现了IOrderedEnumerable接口。而ThenBy扩展方法被加到了IOrderedEnumerable接口对象上,其返回值也是一个具有IOrderedEnumerable接口的对象。

照这么说,调用了一次OrderBy后,然后调用多次ThenBy也是可以工作的。我也从官方MSDN中找到了答案:

ThenBy and ThenByDescending are defined to extend the type IOrderedEnumerable, which is also the return type of these methods. This design enables you to specify multiple sort criteria by applying any number of ThenBy or ThenByDescending methods.

翻译为: ThenBy及ThenByDescending是IOrderedEnumerable类型的扩展方法。ThenBy和ThenByDescending方法的返回值也是IOrderedEnumerable类型。这样设计是为了能够调用任意数量的ThenBy和ThenByDescending方法实现多重排序。

至此,ThenBy的神秘面纱就解开了,但是我不知道如何查看OrderedSequence类的源码,如果能看到这个类的源码就太完美了。知道的同学请告知方法。

注: 上述类的源码来自于Mono的实现。

C#中的ThenBy是如何实现的的更多相关文章

  1. 【转载】C#中使用OrderBy和ThenBy等方法对List集合进行排序

    在C#的List操作中,针对List对象集合的排序我们可以使用OrderBy.OrderByDescending.ThenBy.ThenByDescending等方法按照特定的对象属性进行排序,其中O ...

  2. 年终巨献 史上最全 ——LINQ to SQL语句

    LINQ to SQL语句(1)之Where 适用场景:实现过滤,查询等功能. 说明:与SQL命令中的Where作用相似,都是起到范围限定也就是过滤作用的,而判断条件就是它后面所接的子句.Where操 ...

  3. LINQ to SQL语句(5)之Order By

    适用场景:对查询出的语句进行排序,比如按时间排序等等. 说明:按指定表达式对集合排序:延迟,:按指定表达式对集合排序:延迟,默认是升序,加上descending表示降序,对应的扩展方法是OrderBy ...

  4. LINQ TO SQL 大全

    最近悟出来一个道理,在这儿分享给大家:学历代表你的过去,能力代表你的现在,学习代表你的将来. 十年河东十年河西,莫欺少年穷 学无止境,精益求精 LINQ to SQL语句(1)之Where 适用场景: ...

  5. LINQ to SQL大全

    LINQ to SQL语句 (1)之Where Where操作 适用场景:实现过滤,查询等功能. 说明:与SQL命令中的Where作用相似,都是起到范围限定也就是过滤作用的,而判断条件就是它后面所接的 ...

  6. [转]LINQ To SQL 语法及实例大全

    转载自:http://blog.csdn.net/pan_junbiao/article/details/7015633 LINQ to SQL语句(1)之Where Where操作 适用场景:实现过 ...

  7. LINQ to SQL语句非常详细(原文来自于网络)

    LINQ to SQL语句(1)之Where Where操作 适用场景:实现过滤,查询等功能. 说明:与SQL命令中的Where作用相似,都是起到范围限定也就是过滤作用的,而判断条件就是它后面所接的子 ...

  8. LINQ To SQL 语法及实例大全

    http://blog.csdn.net/pan_junbiao/article/details/7015633 http://blog.csdn.net/pan_junbiao/article/de ...

  9. 转载linq to sql 的详解

    [转]LINQ To SQL 语法及实例大全 2011-11-26阅读38651 评论9 LINQ to SQL语句(1)之Where Where操作 适用场景:实现过滤,查询等功能. 说明:与SQL ...

随机推荐

  1. 抽象数据类型ADT

    ADT(Abstract Data Type) 类型由什么组成? 一个类型(type)指定两类信息,一个属性集和一个操作集. 假设要定义一个新的数据类型.首先,要提供存储数据的方式,可能是通过设计一个 ...

  2. SPI数据传输(库函数方法)

    主机端: /********************************* 代码功能:SPI数据传输(主机端) 引脚说明: SS/CS:片选(高电平屏蔽,低电平启用) MOSI :主机送出信号 M ...

  3. linux磁盘限额和进阶文件系统的管理 quota RAID LVM

    概念: Quota 的一般用途: 针对 WWW server ,例如:每个人的网页空间的容量限制! 针对 mail server,例如:每个人的邮件空间限制. 针对 file server,例如:每个 ...

  4. UILabel的常用属性

    UILabel常用属性1——实例化和设置文字 // 实例化UILabel并指定其边框 UILabel *label = [[UILabel alloc]initWithFrame:CGRectMake ...

  5. eclipse编码格式设置教程、如何为eclipse设置编码格式?

    如果要使插件开发应用能有更好的国际化支持,能够最大程度的支持中文输出,则最好使 Java文件使用UTF-8编码.然而,EcliPSe工 作空间(workspace)的缺省字符编码是操作系统缺省的编码, ...

  6. RGBA 与opacity

    RGBA是一种表示颜色的方式,初次看到觉得很奇怪,与RGB的区别是什么?后面查了下,才发现RGBA的好处. RGBA各个字母的含义为: R:红色值,正整数 | 百分数: G:绿色值,正整数 | 百分数 ...

  7. python学习笔记-Day6(1)

    shelve模块是一个简单的k,v将内存数据通过文件持久化的模块,可以持久化任何pickle可支持的python数据格式 >>> s=shelve.open('test') > ...

  8. Data Base MongoDB 无法创建抽象类的问题,

     无法创建抽象类BsonClassMap.RegisterClassMap 大家都知道抽象类是无法实例化的,即:不能new. 在以下这些情况会遇到这种问题: 1.基类是抽象类: 2.基类是接口: 由于 ...

  9. 使用 BeanCopier 复制对象

    Cglib是一款比较底层的操作java字节码的框架. BeanCopier是一个工具类,可以用于Bean对象内容的复制. 复制Bean对象内容的方法有很多,比如自己手动get set ,或者使用Pro ...

  10. 面试知识点总结之Java语言

    1.如果某个对象出现在字符串表达式中,如System.out.println(this+".class");,则会自动调用this.toString() 2.所有的类都是在对其第一 ...