LINQ排序語法(OrderBy、OrderByDescending、ThenBy、ThenByDescending)的應用
這章我們來說說要如何在LINQ中使用排序的功能整理集合,由於LINQ中的排序其實是一組的語法所組合而成的,所以今天會講到多個不同的語法,雖然說是多個語法,但是關鍵都還是圍繞在排序這個目的上,只是使用的情境會不同而已,讓我們來看看這些語法各有什麼樣的作用吧。
功能說明
OrderBy
設定第一個排序條件,而且此排序條件為遞增排序。
OrderByDescending
設定第一個排序條件,而且此排序條件為遞減排序。
ThenBy
設定第二個以後的排序條件,此排序條件為遞增排序。
ThenByDescending
設定第二個以後的排序條件,此排序條件為遞減排序。
本章會講解以上四個方法,藉由說明我們可以看到它們其實只有順序及遞增遞減的差別而已,性質其實是相同的。
方法定義
這組方法主要目的就是排序資料,請看下面的例子(節錄自Microsoft Docs):
char[] Source = new char[] { 'G', 'C', 'F', 'E', 'B', 'A', 'D' };
IOrderedEnumerable<char> Results = Source.OrderBy(c => c);
foreach(char result in Results){
Console.Write($"{result} ");
}
Console.WriteLine();
// output: A B C D E F G
由上面的例子我們知道了OrdeBy
的作用是將資料遞增排序
,讓我們來看看它的定義:
public static IOrderedEnumerable<TSource> OrderBy<TSource, TKey>(
this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector);
public static IOrderedEnumerable<TSource> OrderBy<TSource, TKey>(
this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector,
IComparer<TKey> comparer);
keySelector
: 要排序的欄位comparer
: 客製的比較器IOrderedEnumerable
: LINQ排序語法都會回傳此型別
OrderBy
有兩個方法,差別在於要不要傳入客製的比較器,客製的比較器是使用在你有特別的排序方式的情況時,等下的範例程式我們做個範例。
這裡我們會看到一個特別的回傳型別IOrderedEnumerable
,它繼承自IEnumerable
,會定義這個型別的目的是為了要讓ThenBy
及ThenByDescending
可以接續在這個排序之後再做其他的排序。
當然,因為IOrderedEnumerable
是繼承自IEnumerable
,所以你要在後面用OrderBy
也是合法的,但是這樣做會讓它忽略之前的排序,將其本身視為第一個排序條件,我們之後的範例程式再來看看。
接下來的OrderByDescending
的方法定義:
public static IOrderedEnumerable<TSource> OrderByDescending<TSource, TKey>(
this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector);
public static IOrderedEnumerable<TSource> OrderByDescending<TSource, TKey>(
this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector,
IComparer<TKey> comparer);
整個方法結構跟OrderBy
完全一樣,只是差在它的結果會是遞減
的。
最後我們來看ThenBy這一組的方法:
public static IOrderedEnumerable<TSource> ThenBy<TSource, TKey>(
this IOrderedEnumerable<TSource> source,
Func<TSource, TKey> keySelector)
public static IOrderedEnumerable<TSource> ThenBy<TSource, TKey>(
this IOrderedEnumerable<TSource> source,
Func<TSource, TKey> keySelector,
IComparer<TKey> comparer)
public static IOrderedEnumerable<TSource> ThenByDescending<TSource, TKey>(
this IOrderedEnumerable<TSource> source,
Func<TSource, TKey> keySelector)
public static IOrderedEnumerable<TSource> ThenByDescending<TSource, TKey>(
this IOrderedEnumerable<TSource> source,
Func<TSource, TKey> keySelector,
IComparer<TKey> comparer)
前面有說到ThenBy
及ThenByDescending
是要接在其他排序的後面,現在看到定義this
的確是需要傳入IOrderedEnumerable
,所以代表你在一個IEnumerable
的後面是不能接ThenBy
的,要先接OrderBy
才能用ThenBy
。
查詢運算式
依據C# Spec可以照到下面跟orderby
相關的定義:
orderby_clause
: 'orderby' orderings
;
orderings
: ordering (',' ordering)*
;
ordering
: expression ordering_direction?
;
ordering_direction
: 'ascending'
| 'descending'
;
orderby
查詢運算式後面可以接多個排序條件(以,
分隔)- 每個查詢條件後可以用
ascending
或descending
來決定要遞增還是遞減
來看一個查詢運算式的例子:
string[] words = new string[] { "Apple", "Banana", "Cherry", "Donut", "Eat", "Football" };
IOrderedEnumerable<string> results = from word in words
orderby word.Substring(1 1),
word.Substring(2,1) descending
select word;
...
- 第一個排序條件為第二個字母遞增
- 第二個排序條件為第三個字母遞減
可以看到查詢運算式的排序是非常直覺的,相較於方法要用各個不同的方法來排序,運算式的語法更像是我們熟悉的SQL。
我們可以依照C# Spec的定義將下面的運算式:
from x in e
orderby k1 , k2 , ..., kn
...
轉成下面的方法:
from x in ( e ) .
OrderBy ( x => k1 ) .
ThenBy ( x => k2 ) .
... .
ThenBy ( x => kn )
...
可以看到查詢運算式所轉出來的方法是按照OrderBy.ThenBy
的方式實作。
方法範例
客製比較器
想要將偶數排在奇數之前,我們可以實作一個客製的比較器如下:
int[] numbers = new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
IOrderedEnumerable<int> results = numbers.OrderBy(x => x, newCustomComparer());
...
class CustomComparer : IComparer<int>
{
public int Compare(int x, int y)
{
return x % 2 - y % 2;
}
}
// output: 0 2 4 6 8 1 3 5 7 9
- 奇數除以二的餘數一定大於偶數,所以排在偶數之後
- 當比較的大小相同時,因為
OrderBy
是Stable Sort所以會依照原本的序列排序
OrderBy
連續使用string[] words = new string[] { "Apple", "Banana", "Cherry", "Donut", "Eat", "Football" };
IOrderedEnumerable<string> results = words
.OrderBy(x =>x.Substring(1, 1))
.OrderByDescending(x => x.Substring(2, 1));
/* output:
*
* Eat
* Apple
* Football
* Banana
* Donut
* Cherry
*/
IOrderedEnumerable<string> results2 = words
.OrderBy(x =>x.Substring(1, 1))
.ThenByDescending(x => x.Substring(2, 1));
/* output:
*
* Eat
* Banana
* Cherry
* Football
* Donut
* Apple
*/
由結果可以看出不用ThenBy
的話第一個查詢條件形同虛設,根本沒有用到。
特別之處
- 屬於延遲執行的方法,回傳的只是查詢的資訊,要等到
GetEnumerator()
或是foreach
觸發才會做巡覽 - 回傳值不是
IEnumerable
而是IOrderedEnumerable
,目的是要讓ThenBy及ThenByDescending接續其後設定其他的排序條件 - 可以是實作
IComparer
來客製比較器
結語
這一章我們學了4個排序方法,OrderBy
家族會排在第一個條件,而ThenBy
家族則是排在第二到第n個條件,利用Descending
搭配讓我們可以遞增遞減排序。