C#中的ThenBy是如何實現的

黃博文發表於2013-12-26

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的實現。

相關文章