CodeGym /课程 /C# SELF /集合排序

集合排序

C# SELF
第 29 级 , 课程 2
可用

1. 入门

如果你有过一个袜子抽屉,想找一双配对的袜子,那你肯定遇到过排序的问题。当一切都乱七八糟的时候,找东西就像打副本一样。在编程里,集合也是同样的道理。

排序就是把集合里的元素按照某个标准(比如字母顺序、数值、日期等等)排个顺序。这很重要,因为:

  • 给用户展示数据(没人喜欢乱糟糟的界面)。
  • 方便查找(比如二分查找只能在排好序的集合里用)。
  • 对比、报表、导出和其他操作。

百万级问题:“如果我只有五个元素,还需要排序吗?”——理论上可以不排,但一旦五变成五百、五千,不自动化就不行了。

.NET里排序主要有两种方式:

  • 直接改原始集合(比如用List<T>Sort方法排序)。
  • 新建一个排好序的集合副本(比如先克隆数组再排序)。

.Sort()方法排序List

Sort()方法是List<T>类自带的,因为它实现了IList<T>接口,支持按索引访问和元素顺序调整。

例子——数字升序排序:


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
        }
    }
}

这里是“原地排序”:原始Listnumbers被改了,元素顺序变了。

字符串排序

字符串排序也很简单:


var words = new List<string> { "橙子", "苹果", "香蕉", "梨" };
words.Sort();
Console.WriteLine(string.Join(", ", words)); // 橙子, 香蕉, 梨, 苹果

有趣的是:默认字符串排序是按Unicode顺序来的,不是“人类”字母表顺序(多语言应用要注意)。

2. 自定义排序规则

有时候默认排序不够用。比如你想按用户年龄或注册日期排序,而不是名字。

用lambda表达式排序(Sort(Comparison<T>)方法)

Sort方法可以传一个自定义的比较函数,来指定排序规则。

按用户年龄排序:


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 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<TKey, TValue>

Dictionary<TKey, TValue>本质上是无序集合(遍历时顺序不保证)。但有时候你想要“排好序的键值对”。

想要排好序的键或值,可以新建List,把键值对复制进去,然后按你想要的方式排序:


var dict = new Dictionary<string, int>
{
    { "苹果", 2 },
    { "橙子", 5 },
    { "梨", 3 }
};

// 按键排序:
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}");
}

// 按值排序:
keyValueList.Sort((a, b) => a.Value.CompareTo(b.Value));
foreach (var kv in keyValueList)
{
    Console.WriteLine($"{kv.Key}: {kv.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,像上面那样。

按字母排序用户列表

比如你有个用户列表:


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}");

排序方式对比

集合 是否原地修改? 排序方法 能自定义规则吗?
List<T>
Sort()
可以:lambda或IComparer
T[]
Array.Sort()
可以:lambda或IComparer
Dictionary<TKey, TValue>
—(新建List再排序) 可以:lambda或IComparer

7. 常见错误和排序细节

原地排序——会改原始集合!如果原始数据不能动——提前复制一份。

试图排序不可变集合(比如ReadOnlyCollection<T>会报运行时错误。

不同文化下字符串比较:字符串排序(尤其是有西里尔字母、变音符号等)在不同地区可能不一样。想要靠谱的排序,用Comparer.Create(...)指定文化。

字典排序不会改结构——你拿到的总是新序列(或新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();
2
任务
C# SELF, 第 29 级, 课程 2
已锁定
按字母顺序排序字符串列表
按字母顺序排序字符串列表
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION