1. 前言
如果你曾經有個襪子抽屜,想找一雙配對的襪子,那你肯定遇過排序的問題。東西亂七八糟的時候,找東西就像在闖關一樣。寫程式處理集合時,其實也是同一回事啦。
排序,就是把集合裡的元素按照某個規則(像是字母順序、數值、日期等等)排好。這很重要,因為:
- 給使用者看資料時(沒人喜歡亂七八糟的畫面)。
- 方便搜尋(像 binary search 只能在已排序的集合用)。
- 比較、報表、匯出資料等等操作。
百萬大哉問:「如果我只有五個元素,是不是不用排序?」——理論上可以,但五個變五百、五千時,不自動化就 GG 了。
在 .NET 裡,排序主要有兩種方式:
- 直接改原本的集合(像 List<T> 用 Sort 方法)。
- 先複製一份,再排序(像複製陣列再排)。
用 .Sort() 排 List
Sort() 是 List<T> 的方法,因為這個 class 有實作 IList<T> 介面,可以用 index 存取,也能改變元素順序。
範例——數字遞增排序:
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
var numbers = new List<int> { 5, 2, 9, 1, 5, 6 };
numbers.Sort();
Console.WriteLine("遞增排序:");
foreach(var number in numbers)
{
Console.Write($"{number} "); // 1 2 5 5 6 9
}
}
}
這裡是「原地排序」:原本的 numbers 會被改掉,元素順序直接變了。
字串排序
字串也可以直接排序:
var words = new List<string> { "柳橙", "蘋果", "香蕉", "梨子" };
words.Sort();
Console.WriteLine(string.Join(", ", words)); // 柳橙, 香蕉, 梨子, 蘋果
有趣的是:預設字串排序是照 Unicode 排,不是「人類」的字母順序(多語言應用要注意)。
2. 自訂排序規則
有時候預設排序不夠用。比如你想照年齡或註冊日期排使用者,而不是名字。
用 lambda 表達式排序(Sort(Comparison<T>) 方法)
Sort 方法可以傳一個自訂的比較規則——也就是比較兩個元素的 function。
照年齡排序使用者:
class User
{
public string Name { get; set; }
public int Age { get; set; }
}
// ...
var users = new List<User>
{
new User { Name = "艾莉絲", Age = 30 },
new User { Name = "鮑伯", Age = 25 },
new User { Name = "伊娃", Age = 35 }
};
users.Sort((u1, u2) => u1.Age.CompareTo(u2.Age));
foreach (var user in users)
{
Console.WriteLine($"{user.Name}: {user.Age}");
}
// 輸出:
// 鮑伯: 25
// 艾莉絲: 30
// 伊娃: 35
這招怎麼運作?lambda (u1, u2) => u1.Age.CompareTo(u2.Age) 會回傳負數代表 u1 比較年輕,正數代表比較老,0 則是年齡一樣。
用 IComparer<T> 介面
有時候你想把排序規則寫成一個 class,像是有很多集合或很複雜的邏輯時。
class UserAgeComparer : IComparer<User>
{
public int Compare(User x, User y)
{
return x.Age.CompareTo(y.Age);
}
}
// ...
var users = new List<User>{ /* ... */ };
users.Sort(new UserAgeComparer()); // 現在照年齡排
這樣很方便,尤其是你在不同地方都要用到同一種排序。
手動多欄位排序
如果有些使用者年齡一樣,但你想在同年齡裡再照名字排:
users.Sort((u1, u2) => {
int ageCompare = u1.Age.CompareTo(u2.Age);
if (ageCompare != 0)
return ageCompare;
else
return u1.Name.CompareTo(u2.Name);
});
這樣就會先照年齡,再照名字排序。
3. 排序集合的複本(不動原本的)
如果你不想動到原本的 list,排序前先複製一份:
var copy = new List<int>(numbers);
copy.Sort();
自訂物件也是一樣:
var usersCopy = new List<User>(users);
usersCopy.Sort((a, b) => a.Age.CompareTo(b.Age));
4. 陣列排序
陣列(T[])也很簡單:
int[] numbers = { 4, 2, 9, 7 };
Array.Sort(numbers); // 會改到原本的陣列
要自訂排序規則也可以:
Array.Sort(numbers, (a, b) => b.CompareTo(a)); // 遞減排序
注意 Array.Sort 會直接改原本的陣列,不會回傳新陣列。如果想保留原本的,記得先複製:
int[] oldNumbers = { 3, 2, 1 };
int[] copy = (int[])oldNumbers.Clone();
Array.Sort(copy);
5. 「排序」Dictionary(Dictionary<TKey, TValue>)
Dictionary<TKey, TValue> 本質上是無序的(也就是列舉時順序不保證)。但有時候你還是想要「排序過的 key-value 對」。
要拿到排序過的 key 或 value,先把 pair 複製到 list,再照你要的方式排:
var dict = new Dictionary<string, int>
{
{ "蘋果", 2 },
{ "柳橙", 5 },
{ "梨子", 3 }
};
// 照 key 排序:
var keyValueList = new List<KeyValuePair<string, int>>(dict);
keyValueList.Sort((a, b) => a.Key.CompareTo(b.Key));
foreach (var kv in keyValueList)
{
Console.WriteLine($"{kv.Key}: {kv.Value}");
}
// 照 value 排序:
keyValueList.Sort((a, b) => a.Value.CompareTo(b.Value));
foreach (var kv in keyValueList)
{
Console.WriteLine($"{kv.Key}: {kv.Value}");
}
如果你只想要排序過的 key 或 value:
var sortedKeys = new List<string>(dict.Keys);
sortedKeys.Sort();
var sortedValues = new List<int>(dict.Values);
sortedValues.Sort();
但記住,Dictionary 結構本身不會變——你只是拿到排序過的列舉而已。到 .NET 9 會有 OrderedDictionary<TKey, TValue>,它會記住元素順序。雖然不常用,但有時候真的會需要。
6. 有用的小細節
一定要實作比較介面嗎?
很簡單:如果你想讓你的物件「知道」怎麼比較自己(像是照日期或名字),就實作 IComparable<T> 介面。
class Product : IComparable<Product>
{
public string Name { get; set; }
public decimal Price { get; set; }
public int CompareTo(Product other)
{
return Price.CompareTo(other.Price);
}
}
這樣你就可以:
var products = new List<Product> { /* ... */ };
products.Sort(); // 會照價格排
如果預設比較不合用——就用 IComparer<T> 或 lambda,像上面那樣。
照字母排序使用者清單
假設你的 app 有個使用者清單:
List<string> users = new List<string> { "維克多", "安娜", "葉卡捷琳娜", "鮑里斯" };
users.Sort(); // 現在 users 排好了:安娜, 鮑里斯, 維克多, 葉卡捷琳娜
依照緊急程度分配任務
class Task
{
public string Title { get; set; }
public int Priority { get; set; } // 1 - 緊急, 2 - 重要, 3 - 可以等等
}
var todo = new List<Task>
{
new Task { Title = "寫作業", Priority = 2 },
new Task { Title = "買麵包", Priority = 1 },
new Task { Title = "追劇", Priority = 3 }
};
todo.Sort((a, b) => a.Priority.CompareTo(b.Priority));
foreach (var task in todo)
Console.WriteLine($"{task.Priority}: {task.Title}");
排序方法比較
| 集合 | 會原地改變嗎? | 排序方法 | 能自訂規則嗎? |
|---|---|---|---|
|
會 | |
可以:lambda 或 IComparer |
|
會 | |
可以:lambda 或 IComparer |
|
不會 | —(先做 list 再排) | 可以:lambda 或 IComparer |
7. 常見錯誤和排序小陷阱
原地排序——會直接改原本的集合!如果原始資料不能動,記得先複製。
想排不能改的集合(像 ReadOnlyCollection<T>) 會直接噴 runtime error。
不同文化下字串比較會不一樣:字串排序(尤其有西里爾字母、變音符號等)在不同地區設定下會不一樣。要正確排序,請用 Comparer.Create(...) 並指定文化。
Dictionary 排序不會改變結構——你拿到的永遠是新序列(或新 list)。
複雜排序請用 IComparer<T> 加你自己的邏輯。
這是錯誤排序的範例(實務上常踩雷):
var numbers = new List<int> { 1, 2, 3 };
var sorted = numbers.Sort(); // 錯誤:Sort() 回傳 void!
正確寫法:
numbers.Sort(); // 直接改 numbers
// 如果要新集合:
var sorted = new List<int>(numbers);
sorted.Sort();
GO TO FULL VERSION