C# Dictionary 入门:用键值对告别低效遍历

橘子海
橘子海
发布时间:2026-08-24 | 128人浏览过
原创

前言

C# 初学者很容易把很多“按条件找数据”的逻辑写成循环遍历,但只要数据天然带有唯一标识,Dictionary<TKey, TValue> 往往就是更高效的选择。本文从创建方式、增删改查到遍历注意事项逐步拆开,并结合异常行为与复杂度差异,帮你判断它在实际业务里该怎么用、什么时候用得最顺手。

C# 里很多“先遍历再匹配”的写法,其实都可以换成 Dictionary 来直接按键定位数据。它的优势不只是写法更简洁,更关键的是查找、添加、删除的平均复杂度接近 O(1),在用户信息、商品库存、配置项这类按标识取值的场景里尤其合适。

下面按日常开发最常见的问题来拆解:先看它适合解决什么问题,再看怎么创建、怎么安全地读写,以及遍历时有哪些容易踩坑的地方。看完你基本可以判断,什么时候该用字典,什么时候该避免继续手写低效遍历。

Dictionary 是什么,适合解决什么问题

Dictionary<TKey, TValue> 本质上是一个“键值对”集合:

  • 通过 Key 快速查找 Value
  • 查找、添加、删除的平均复杂度接近 O(1)
  • 底层是哈希表(hash table)

它特别适合“用唯一标识换取某个对象或数据”的场景,例如:

  • 用户ID(Key) → 用户对象(Value)
  • 商品编码 → 商品信息
  • 配置项名称 → 配置值
  • 状态码 → 描述字符串

使用时需要引入命名空间:

using System.Collections.Generic;

如果你的代码经常出现“在列表里循环查某个编号、名称或状态”的操作,那么通常就值得考虑改成 Dictionary。

怎么创建 Dictionary

无参构造

var dict = new Dictionary<string, int>();
  • Key 类型:string
  • Value 类型:int
  • 初始容量默认,会按需要自动扩容

指定初始容量

var dict = new Dictionary<string, int>(capacity: 1000);

当你大概知道会放多少条数据时,这种写法更实用:

用白底结构图对比 C# Dictionary 的几种创建方式与适用场景
Dictionary 创建方式一图看懂把无参构造、指定容量和初始化器放在一张图里,更容易看清各自适用条件。
  • 减少扩容次数,性能更稳定
  • 适合数据量较大、批量装载的场景

直接初始化数据

如果一开始就有固定数据,可以直接用集合初始化器:

var dict = new Dictionary<string, int>
{
    { "apple", 3 },
    { "banana", 5 },
};

也可以使用索引器形式:

var dict = new Dictionary<string, int>
{
    ["apple"] = 3,
    ["banana"] = 5,
};

两种都常见,团队里保持风格统一即可。

增删改查怎么写,哪些方法更安全

var stock = new Dictionary<string, int>();

添加:Add 和索引器怎么选

// 方式1:Add
stock.Add("apple", 10);
stock.Add("banana", 5);

// 方式2:索引器
stock["orange"] = 8;    // orange 不存在时 → 添加
stock["orange"] = 12;   // orange 已存在时 → 覆盖为 12

两者差别很关键:

对比 Add、索引器和 TryGetValue 的行为差异,帮助选择更合适的读写方式
Add、索引器、TryGetValue 的选这张图聚焦最常用的增删改查判断:重复键是报错、覆盖还是分支处理。
  • Add(key, value):如果 Key 已存在,会抛 ArgumentException
  • stock[key] = value:Key 不存在时添加,已存在时覆盖

可以这样判断:

  • 重复 Key 在业务上意味着 Bug,就用 Add
  • 重复 Key 代表更新数据,就用索引器

读取:为什么更推荐 TryGetValue

// 已经有一些数据
stock["apple"] = 10;

// 方式1:索引器
int appleCount = stock["apple"];  // 如果 apple 不存在会抛 KeyNotFoundException

// 方式2:TryGetValue(推荐)
if (stock.TryGetValue("banana", out int bananaCount))
{
    Console.WriteLine($"banana: {bananaCount}");
}
else
{
    Console.WriteLine("banana 不存在");
}

这里的经验很简单:

  • 你能确定 Key 一定存在,可以直接用索引器
  • 只要存在“不一定有这个 Key”的可能,优先用 TryGetValue

这样可以少写异常处理,也更符合日常业务代码的预期分支。

修改:覆盖和累加分别怎么写

直接修改已有值时,用索引器赋值即可:

// 已有 "apple" → 10
stock["apple"] = 15; // 覆盖为 15

如果需求是“在原有值基础上累加”,可以把 TryGetValue 和索引器结合起来:

void AddStock(string name, int delta)
{
    stock.TryGetValue(name, out int current); // 不存在时 current=0
    stock[name] = current + delta;
}

// 用法:
AddStock("apple", 5);  // apple: 10 → 15
AddStock("pear", 3);   // pear: 0  → 3(新增)

这种写法很适合库存、计数器、访问次数统计这类场景。

删除:Remove 和 Clear

// 删除某个键值对
bool removed = stock.Remove("apple");   // 删除成功返回 true,不存在返回 false

// 清空所有数据
stock.Clear();

Remove 适合单项删除,Clear 适合整体重置。前者有布尔返回值,做业务判断时很方便。

几个必须知道的属性和方法

Count:当前元素个数

Console.WriteLine(stock.Count);

判断字典大小、是否为空、是否达到预期装载量时都会用到它。

Keys 和 Values:获取所有键或值

var keys = stock.Keys;       // ICollection<string>
var values = stock.Values;   // ICollection<int>

stock["apple"] = 33;

foreach (var name in stock.Keys)
{
    Console.WriteLine(name);
}

foreach (var count in stock.Values)
{
    Console.WriteLine(count);
}

这里有一个容易忽略的点:

  • Keys / Values 是引用视图,不是复制出来的新集合
  • 原字典发生变化,它们也会感知到变化

ContainsKey 和 ContainsValue 的差别

bool hasApple = stock.ContainsKey("apple");
bool hasCount10 = stock.ContainsValue(10);

这两个名字看着接近,性能差异却很大:

  • ContainsKey:平均 O(1),通常很快
  • ContainsValue:需要遍历全部 Value,复杂度 O(n),大字典里要慎用

也就是说,Dictionary 的核心优势是“按 Key 查”,而不是“按 Value 反查”。

如何正确遍历 Dictionary,避免边遍历边修改

遍历键值对

foreach (var kv in stock)
{
    Console.WriteLine($"水果:{kv.Key},库存:{kv.Value}");
}

这是最常规也最直观的遍历方式,适合同时需要 Key 和 Value 的情况。

展示 Dictionary 遍历方式与遍历中修改集合的风险,说明正确删除零库存项的处理流程
遍历方式与修改风险遍历本身不复杂,真正容易出错的是边遍历边删除,这里用流程图更直观。

解构写法

foreach (var (name, count) in stock)
{
    Console.WriteLine($"{name} => {count}");
}

当你更喜欢简洁变量名时,解构写法会更顺手,可读性也不错。

只遍历 Key 或只遍历 Value

foreach (var name in stock.Keys)
{
    Console.WriteLine(name);
}

foreach (var count in stock.Values)
{
    Console.WriteLine(count);
}

如果你只需要键,或者只需要值,就没必要再遍历完整的键值对。

为什么遍历时不能直接修改字典

下面这种写法在运行时会抛出 InvalidOperationException:

foreach (var (name, count) in stock)
{
    if (count == 0)
    {
        stock.Remove(name); // 遍历中修改集合 → 异常
    }
}

更稳妥的做法是先记录要删除的 Key,再统一删除:

var toRemove = new List<string>();

foreach (var (name, count) in stock)
{
    if (count == 0)
        toRemove.Add(name);
}

foreach (var name in toRemove)
{
    stock.Remove(name);
}

这类“先收集、后修改”的写法,在处理集合时很常见,也值得形成习惯。

什么时候优先考虑用 Dictionary

如果你的需求符合下面几类,Dictionary 往往就是更直接的选择:

  • 通过唯一标识快速找到对象或数据
  • 频繁做查找、更新、删除操作
  • 希望把“遍历匹配”改成“按键直取”

反过来说,如果你的数据并不是围绕 Key 组织,或者经常需要按 Value 做复杂筛选,那它就未必是最合适的结构。

对大多数 C# 初学者来说,先把 Add、索引器、TryGetValue、ContainsKey 和正确遍历这几件事用熟,基本就能覆盖绝大部分日常开发场景。

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表