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