Операции ThenBy и ThenByDescending
43LINQ --- LINQ to Objects --- Операции ThenBy и ThenByDescending
Вызовы ThenBy и ThenByDescending могут соединяться в цепочку, т.к. они принимают в качестве входной последовательности IOrderedEnumerable<T> и возвращают в качестве выходной последовательности тоже IOrderedEnumerable<T>.
Например, следующая последовательность вызовов не разрешена:
inputSequence.OrderBy(s => s.LastName).OrderBy(s => s.FirstName)...
Вместо нее должна использоваться такая цепочка:
inputSequence.OrderBy(s => s.LastName).ThenBy(s => s.FirstName)...
ThenBy
Операция ThenBy позволяет упорядочивать входную последовательность типа IOrderedEnumerable<T> на основе метода keySelector, который возвращает значение ключа. В результате выдается упорядоченная последовательность типа IOrderedEnumerable<T>.
В отличие от большинства операций отложенных запросов LINQ to Objects, операции ThenBy и ThenByDescending принимают другой тип входных последовательностей - IOrderedEnumerable<T>. Это значит, что сначала должна быть вызвана операция OrderBy или OrderByDescending для создания последовательности IOrderedEnumerable, на которой можно затем вызывать операции ThenBy и ThenByDescending.
Сортировка, выполняемая операцией ThenBy, является устойчивой. Другими словами, она сохраняет входной порядок элементов с эквивалентными ключами. Если два входных элемента поступили в операцию ThenBy в определенном порядке, и ключевое значение обоих элементов одинаково, то порядок тех же выходных элементов гарантированно сохранится. В отличие от OrderBy и OrderByDescending, операции ThenBy и ThenByDescending выполняют устойчивую сортировку.
Операция ThenBy имеет два прототипа, которые описаны ниже:
- Первый прототип ThenBy
-
public static IOrderedEnumerable<T> ThenBy<T, K> ( this IOrderedEnumerable<T> source, Func<T, K> keySelector) where К : IComparable<K>;
В этом прототипе операции ThenBy упорядоченная входная последовательность типа IOrderedEnumerable<T> передается операции ThenBy наряду с делегатом метода keySelector. Метод keySelector принимает элемент типа T и возвращает поле внутри элемента, которое используется в качестве значения ключа с типом К для входного элемента. Типы T и К могут быть как одинаковыми, так и различными. Значение, возвращенное методом keySelector, должно реализовывать интерфейс IComparable. Операция ThenBy упорядочит входную последовательность по возрастанию на основе возвращенных ключей.
- Второй прототип ThenBy
-
public static IOrderedEnumerable<T> ThenBy<T, K>( this IOrderedEnumerable<T> source, Func<T, K> keySelector, IComparer<K> comparer);
Этот прототип подобен первому, за исключением того, что принимает объект-компаратор. Если используется эта версия операции ThenBy, то типу К не обязательно реализовывать интерфейс IComparable.
Ниже показан пример использования первого прототипа:
string[] cars = { "Alfa Romeo", "Aston Martin", "Audi", "Nissan", "Chevrolet", "Chrysler", "Dodge", "BMW",
"Ferrari", "Bentley", "Ford", "Lexus", "Mercedes", "Toyota", "Volvo", "Subaru", "Жигули :)"};
IEnumerable<string> auto = cars.OrderBy(s => s.Length).ThenBy(s => s);
foreach (string str in auto)
Console.WriteLine(str);
Этот код сначала упорядочивает элементы по их длине, в данном случае — длине названия автомобиля. Затем упорядочивает по самому элементу. В результате получается список названий, отсортированный по длине от меньшей к большей (по возрастанию), а затем — по имени в алфавитном порядке:

В примере применения второго прототипа ThenBy снова будет использоваться показанный в предыдущей статье объект-компаратор MyVowelToConsonantRatioComparer. Однако перед вызовом ThenBy сначала потребуется вызвать либо OrderBy, либо OrderByDescending. В данном примере будет вызван OrderBy, чтобы упорядочить список по количеству символов в имени. Таким образом, имена будут упорядочены по возрастанию количества букв в них, а затем — внутри каждой группы с одинаковой длиной — по соотношению гласных и согласных:
MyVowelToConsonantRatioComparer myComp = new MyVowelToConsonantRatioComparer();
IEnumerable<string> auto = cars
.OrderBy(s => s.Length)
.ThenBy((s => s), myComp);
foreach (string str in auto)
{
int vCount = 0;
int cCount = 0;
myComp.GetVowelConsonantCount(str, ref vCount, ref cCount);
double dRatio = Math.Round(((double)vCount / (double)cCount), 3);
Console.WriteLine(str + " - " + dRatio + " - " + vCount + ":" + cCount);
}

Как и следовало ожидать, имена сначала упорядочены по длине, а затем — по соотношению гласных и согласных.
ThenByDescending
Эта операция прототипирована и ведет себя подобно операции ThenBy, за исключением того, что упорядочивает элементы по убыванию. Ниже показан пример использования операции ThenByDescending:
string[] cars = { "Alfa Romeo", "Aston Martin", "Audi", "Nissan", "Chevrolet", "Chrysler", "Dodge", "BMW",
"Ferrari", "Bentley", "Ford", "Lexus", "Mercedes", "Toyota", "Volvo", "Subaru", "Жигули :)"};
IEnumerable<string> auto = cars.OrderBy(s => s.Length).ThenByDescending(s => s);
foreach (string str in auto)
Console.WriteLine(str);
В примере применения первого прототипа операции ThenByDescending используется тот же базовый подход, что и в примере вызова первого прототипа операции ThenBy, за исключением того, что вместо ThenBy будет вызываться ThenByDescending.
Этот код порождает вывод, где имена в пределах группы с одной и той же длиной сортируются по алфавиту в обратном порядке тому, который обеспечивает операция ThenBy:
