C#中給繼承自IEnumerable的對象(最熟知的就是List了)提供了很豐富的擴展方法,涉及列表操作的方方面面。而擴展方法ThenBy就是很有意思的一個,它的實現也很巧妙。
如果有這樣的一個Team類,里面有三個屬性。
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 |
|
然后我們有一個Team的List。
1 2 3 4 5 |
|
那么如何求出teams中得分最高的那個隊伍那?這個很簡單,只需要一句話即可。
1 2 |
|
由於List實現了IEnumerable接口,而System.Linq中的Enumerable類中有針對IEnumerable接口的名為OrderByDescending的擴展方法,所以我們直接調用這個擴展方法可以對List按照指定的key進行降序排列,再調用First這個擴展方法來獲取列表中的第一個元素。
如果我的List變成這個樣子。
1 2 3 4 |
|
由於有可能兩組以上的隊伍都可能拿到最高分,那么在這些最高分的隊伍中,我們選取用時最少的作為最終優勝者。有人說那可以這樣寫。
1
|
|
先對列表按Score降序排列,再對列表按TimeCost升序排列,然后取結果中的第一個元素。看來貌似是正確的,但其實是錯誤的。因為第一次調用OrderByDescending方法后返回了一個排序后的數組,再調用OrderBy是另外一次排序了,它會丟棄上一次排序,這與我們定的先看積分,如果積分相同再看耗時的規則違背。
那么應該如何實現那?C#給我們提供了一個叫做ThenBy的方法,可以滿足我們的要求。
1 2 3 |
|
新的問題又來了。第一次調用OrderByDescending方法時返回的是一個新對象,再對這個新對象調用ThenBy時,它只有記錄了上一次排序規則,才能達到我們想要的效果。那么C#是如何記錄上次排序使用的key那?
這就先要看OrderByDescending方法是如何實現了的。查看源碼發現OrderByDescending有兩個重載,實現如下。
1 2 3 4 5 6 7 8 9 10 11 |
|
在第二個重載中我們看到OrderByDescending方法返回時的是一個繼承了IOrderedEnumerable接口的對象OrderedSequence。這個對象記錄了我們的排序規則。
而我們再查看下ThenBy方法的定義。
1 2 3 4 5 6 7 8 9 10 |
|
我們可以看到ThenBy這個擴展方法追加到的對象類型要實現IOrderedEnumerable接口,而OrderBy方法恰好返回的就是這個類型接口對象。那我們再看看IOrderedEnumerable接口的定義。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
其繼承自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的實現。