文章出處

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


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()