C#中的ThenBy是如何實現的


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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM