C# LINQ 排序详解 ------ OrderBy / OrderByDescending / ThenBy / ThenByDescending
在 C# 中,对集合进行排序最常用的方式是 LINQ 提供的排序运算符:OrderBy、OrderByDescending、ThenBy、ThenByDescending。它们支持单级排序和多级组合排序,且不会修改原集合,返回新的有序序列。
一、核心 API 速览
| 方法 | 说明 | 返回值类型 |
|---|---|---|
OrderBy(key) |
按指定键升序排列(主排序) | IOrderedEnumerable<T> |
OrderByDescending(key) |
按指定键降序排列(主排序) | IOrderedEnumerable<T> |
ThenBy(key) |
在主排序基础上按指定键升序二次排序 | IOrderedEnumerable<T> |
ThenByDescending(key) |
在主排序基础上按指定键降序二次排序 | IOrderedEnumerable<T> |
⚠️
ThenBy / ThenByDescending只能 跟在OrderBy / OrderByDescending后面调用,因为它们扩展的是IOrderedEnumerable<T>,而非普通IEnumerable<T>。
二、准备测试数据
csharp
using System;
using System.Collections.Generic;
using System.Linq;
class Student
{
public string Name { get; set; }
public int Age { get; set; }
public double Score { get; set; }
}
var students = new List<Student>
{
new Student { Name = "张三", Age = 20, Score = 88 },
new Student { Name = "李四", Age = 22, Score = 95 },
new Student { Name = "王五", Age = 20, Score = 95 },
new Student { Name = "赵六", Age = 22, Score = 88 }
};
三、基础用法 ------ 单字段排序
3.1 升序 OrderBy
csharp
// 按年龄升序
var r1 = students
.OrderBy(s => s.Age)
.ToList();
等价查询表达式语法:
csharp
var r1 = (from s in students
orderby s.Age
select s).ToList();
3.2 降序 OrderByDescending
csharp
// 按分数降序
var r2 = students
.OrderByDescending(s => s.Score)
.ToList();
等价查询表达式:
csharp
var r2 = (from s in students
orderby s.Score descending
select s).ToList();
四、多级排序 ------ ThenBy / ThenByDescending
常见需求:先按年龄升序,年龄相同再按分数降序,分数还相同再按姓名升序
csharp
var r3 = students
.OrderBy(s => s.Age) // 主排序:年龄 ↑
.ThenByDescending(s => s.Score) // 次排序:分数 ↓
.ThenBy(s => s.Name) // 再次排序:姓名 ↑
.ToList();
查询结果顺序为:
| 姓名 | 年龄 | 分数 |
|---|---|---|
| 张三 | 20 | 88 |
| 王五 | 20 | 95 |
| 赵六 | 22 | 88 |
| 李四 | 22 | 95 |
等价查询表达式(逗号分隔多条件):
csharp
var r3 = (from s in students
orderby s.Age, s.Score descending, s.Name
select s).ToList();
五、常见误区
❌ 连续调用两个 OrderBy
csharp
// 错误理解:以为先按 Age 排再按 Score 排
var wrong = students
.OrderBy(s => s.Age)
.OrderBy(s => s.Score); // ← 这会【重置】主排序,Age 的排序被丢弃!
✅ 正确做法:只有第一个用 OrderBy,后续必须用 ThenBy。
❌ 对字符串按长度排序时忘记指定 keySelector
csharp
string[] fruits = { "apple", "banana", "pear", "watermelon" };
// 按字符串长度升序
var r = fruits.OrderBy(f => f.Length).ToList();
// 结果:pear(4), apple(5), banana(6), watermelon(10)
六、自定义比较器(IComparer)
如需特殊规则(如中文按拼音、忽略大小写等):
csharp
var r = students
.OrderBy(s => s.Name, StringComparer.CurrentCultureIgnoreCase)
.ToList();
也可实现自定义 IComparer<T> 传入 OrderBy / ThenBy。
七、关键特性总结
- ♻️ 延迟执行(Deferred Execution) :排序在遍历(
foreach/ToList())时才真正执行 - 🔒 稳定排序:相等键的元素保留原始相对顺序
- 📦 原集合不变 :返回新
IOrderedEnumerable<T>序列 - 🔗 链式调用 :支持无限级
ThenBy / ThenByDescending
八、一句话记忆
排序永远只有一个
OrderBy/OrderByDescending(主排序),其余次要条件全用ThenBy/ThenByDescending追加。