SortedList本质是双数组同步维护的有序映射,Add不覆盖重复键而抛ArgumentException;索引器赋值可安全更新或插入;遍历天然有序因每次增删均二分定位并移动元素;键对象必须全程不可变。

SortedList
为什么 Add(key, value) 一加重复键就崩?
因为 Add 方法语义明确:只负责新增,不覆盖。键已存在时直接抛 ArgumentException,不是 bug,是设计契约。
- 想安全更新或插入?改用索引器赋值:
sortedList[key] = newValue—— 键存在则替换,不存在则插入 - 想先判断再操作?用
ContainsKey(key)+Add,但注意这不是原子操作,多线程下仍可能竞态 - 非泛型
SortedList(来自System.Collections)已不推荐用于新项目,一律用泛型System.Collections.Generic.SortedList
遍历时为啥永远按键升序?内部怎么维持的?
它根本没在遍历时排序——而是每次 Add 或索引器赋值时,就用二分查找定位位置,然后把键数组和值数组里后续元素整体后移一位腾出空位。所以:
- 查找是
O(log n),比Dictionary的O(1)慢;但遍历是O(n)且天然有序,不用额外OrderBy -
Keys[2]和Values[2]是真实数组下标,不是哈希映射结果;IndexOfKey("abc")返回的是当前排序位置,不是插入顺序 -
RemoveAt(5)后,原索引 6 及之后所有项都会前移,循环中边删边用索引要格外小心,容易跳项或越界
Keys 和 Values 属性能直接改吗?
不能。Keys 和 Values 返回的是只读视图(IReadOnlyCollection / IReadOnlyCollection),底层直接包装内部数组,不是副本。
- 对
Keys调用Add、Clear等会立刻抛NotSupportedException - 遍历时放心用
foreach (var k in sortedList.Keys),顺序确定、无装箱、性能好 - 如果需要可变副本(比如要排序后再改),必须显式创建:
new List(sortedList.Keys)
什么时候该选 SortedList,而不是 Dictionary 或 List?
核心看是否同时需要「按键快速查」+「按序遍历/取范围/索引访问」。
- 适合:
GetByIndex(i)取第 N 个元素、实现 LRU 缓存、时间序列配置表、带序号的映射关系 - 不适合:高频随机插入/删除又不关心顺序——
Dictionary更快;纯顺序遍历且键不重要——List+ 手动OrderBy更轻量 -
TrimToSize()能释放冗余内存,但下次扩容又会重新分配,高频增删场景慎用
最易被忽略的一点:键对象必须在整个生命周期内不可变。如果 TKey 是自定义类,且其参与比较的字段后续被修改,会导致内部数组错位、查找失效甚至死循环。










