1. 集合 OrderedDictionary
.NET 一直都很重视丰富的标准库,但程序员的生活总有点妥协:有时候你想要一个能记住添加顺序的字典,或者需要一个不可变的唯一值集合。在.NET 9之前,只能用第三方库或者自己造轮子(有人甚至直接去 GitHub 抄 OrderedDictionary —— 嘘,我们不会告诉别人的)。
.NET 9出来后,有了新的通用集合 —— 终于不用再写奇怪的hack了!咱们来详细聊聊这俩最实用的:OrderedDictionary<TKey, TValue> 和 ReadOnlySet<T>。
1. 什么是 OrderedDictionary?
OrderedDictionary 就是字典和列表的混合体。它像普通 Dictionary<TKey, TValue> 一样存“键-值”对,但保证元素顺序和你添加时一模一样。这个在你需要按用户输入顺序遍历元素,或者顺序影响业务逻辑(比如打印报表、数据序列化、生成配置)时特别有用。
类比普通字典就像一个有很多格子的柜子,随便塞东西进去,顺序无所谓;OrderedDictionary 就像一个有抽屉的整齐柜子,啥先放、啥后放你都一清二楚。
2. 和 Dictionary 的主要区别
- Dictionary:元素顺序不保证(别看有时候顺序对了——别信,这是假象!)。
- OrderedDictionary:元素顺序就是你添加的顺序。
3. 语法和基本方法
来个基础用法例子:
using System.Collections.Generic;
var od = new OrderedDictionary<string, int>();
od.Add("伊万", 5);
od.Add("斯维特兰娜", 8);
od.Add("阿列克斯", 3);
// 按添加顺序遍历:
foreach (var pair in od)
{
Console.WriteLine($"{pair.Key}: {pair.Value}");
}
输出:
伊万: 5
斯维特兰娜: 8
阿列克斯: 3
如果你用普通字典写同样的例子,顺序大概率会变。但用 OrderedDictionary,顺序永远和你加的一样。
OrderedDictionary 实现了和普通字典一样的接口:
- IDictionary<TKey, TValue>
- IReadOnlyDictionary<TKey, TValue>
- IEnumerable<KeyValuePair<TKey, TValue>>
4. 按索引和按键访问
特别之处:OrderedDictionary 既能用键索引,也能用序号索引!
// 按名字(键):
int svetlanaScore = od["斯维特兰娜"]; // 8
// 按索引:
var firstEntry = od.ElementAt(0); // KeyValuePair<string, int>("伊万", 5)
5. 更新和删除
如果你加了一个已存在的键,会抛异常。想替换值——用索引器就行:
od["伊万"] = 10; // 改已存在的,顺序不变
可以按键删,也能按索引删:
od.Remove("斯维特兰娜");
od.RemoveAt(0); // 删除“伊万”
6. 结构可视化
flowchart LR
A("0: 伊万 - 5")
B("1: ス维特兰娜 - 8")
C("2: 阿列克斯 - 3")
A --> B --> C
7. 坑和常见错误
很多开发者用 Dictionary<TKey, TValue> 还指望顺序——这是个坑!有时候顺序对,下次就乱了(尤其.NET升级或换平台时)。
OrderedDictionary 因为要记顺序,某些操作(比如中间插入、删除)会比普通字典慢一点,但绝大多数场景下优点更大。
如果你经常要按索引查找——就用 OrderedDictionary;只要按键查找、顺序无所谓——用普通字典就行。
8. 应用里的实际例子
比如你的人事管理应用现在要出个报表,员工顺序要和添加时一样:
var employeeScores = new OrderedDictionary<string, int>();
employeeScores.Add("彼得", 100);
employeeScores.Add("安娜", 150);
employeeScores.Add("维多利亚", 80);
// 报表顺序永远对:
foreach (var pair in employeeScores)
{
Console.WriteLine($"{pair.Key}: {pair.Value} 分");
}
2. 集合 ReadOnlySet<T>
1. 什么是 ReadOnlySet?
ReadOnlySet<T> 是不可变(immutable)的唯一值集合。以前想要只读 set,只能用 .ToHashSet() 返回副本,或者自己写包装。现在有了这个集合,创建后谁都不能加删元素。这样代码更安全,也防止外部误改数据。
类比就像你记下独一无二的名字,然后把本子塑封了。没人能再写、撕、改。
2. 创建 ReadOnlySet
最简单的创建方式——用扩展方法:
var colors = new[] { "红色", "绿色", "蓝色", "绿色" };
var readOnlyColors = colors.ToReadOnlySet();
// 现在只有唯一值,不能改:
foreach (var color in readOnlyColors)
Console.WriteLine(color);
输出:
红色
绿色
蓝色
也可以直接用构造函数(如果需要):
var set = new ReadOnlySet<int>(new[] {1, 2, 2, 3, 5, 1}); // 还是只有1,2,3,5
3. 主要属性和方法
- 只读(immutable)
- Count —— 元素数量
- Contains(item) —— 判断元素是否存在
- 支持LINQ查询(IEnumerable<T>)
- 查找快,但不能加、删、清空
例子:
if (readOnlyColors.Contains("红色"))
Console.WriteLine("有红色!");
不能这样:
// 编译错误!
readOnlyColors.Add("黄色");
4. 有啥用?
很多时候你要把集合暴露出去,但又不想让别人误操作(或者恶意)改它。比如:
- 返回支持的用户角色列表
- 文件允许的扩展名列表
- 不能改的唯一配置参数列表
比如在你的应用里,有个允许的部门列表:
public static ReadOnlySet<string> Departments { get; } =
new[] { "人事", "开发", "会计" }.ToReadOnlySet();
现在谁也不能从外部改部门列表了。
5. 可视化:和普通HashSet的区别
graph LR
A[HashSet] -- Add/Remove/Contains --> B((元素))
C[ReadOnlySet] -- Only Contains --> D((元素))
3. OrderedDictionary vs. ReadOnlySet:区别表
| 集合 | 存键值对? | 保证顺序 | 能改吗 | 能按键查 | 能按索引查 | 适用场景 |
|---|---|---|---|---|---|---|
| OrderedDictionary | 是,“键-值” | 是 | 是 | 是 | 是 | 地图、配置、报表 |
| ReadOnlySet | 否,只存值 | 否 | 否 | 是 | 否 | 安全集合、常量 |
| HashSet | 否,只存值 | 否 | 是 | 是 | 否 | 需要修改的集合 |
| Dictionary | 是,“键-值” | 否 | 是 | 是 | 否 | 不要求顺序的映射 |
GO TO FULL VERSION