1. 入门
想象一下,你是个小店老板,有一份商品清单。每个商品都有独一无二的货号(比如ART-001、ART-002),当然还有名字、价格、库存数量。
如果你把这些都放在List<T>里,T比如说是我们以后要写的Product类,那你要找货号ART-005的商品时,就得一个个翻:
“这是ART-001吗?不是。这是ART-002?也不是……哦,这个是ART-005!找到了!”
要是只有10个商品还好,要是有1万个、10万个呢?每次找都得花老半天。顾客等你找他最爱的饼干都要等到天荒地老了,这不行!
我们需要一种方法,只要知道唯一的货号就能立刻定位到商品,不用全都翻一遍。也就是说,我们需要一个“key”,能直接指向我们要的“value”。
认识Dictionary
这时候Dictionary<TKey, TValue>就闪亮登场啦!你可以把它想成超级智能的电话本。普通电话本是通过名字(key)找电话号码(value)。你翻到“A”,找到“阿乐”,电话号码就出来了。你不用把所有号码都翻一遍。
Dictionary(翻译过来就是“字典”)也是这样,它把数据存成“键-值”对。
- 键(TKey):每个元素的唯一标识。就像电话本里的名字或者商品的货号。你就是用key来找value的。key在同一个字典里必须唯一。如果你想加一个已经有的key,Dictionary会直接拒绝你。
- 值(TValue):你想存的数据,比如电话号码、商品价格、术语解释,啥都行!
TKey和TValue这俩尖括号里的字母,意思是Dictionary是泛型(Generic)集合。你自己决定key是什么类型,value是什么类型。key可以是string(名字、货号)、int(用户ID),甚至你自己写的类。value也可以是int、string、double或者对象。
优点?极速访问!因为它内部有特殊结构(叫哈希表,别慌,先不用懂原理),Dictionary能在极短时间内通过key找到value,不管你有10个还是100万个元素。就像图书馆的超级索引:你说“我要C#的书”,马上就能告诉你在哪,不用一排排翻。
咱们直接上手吧!我们要做个互动的“C#术语小字典”,边学边记新概念。
2. 基本语法:创建字典
一切都很常规:声明变量,不过这次要写两个类型——key的类型(TKey)和value的类型(TValue):
// 简单字典:key是string(登录名),value是string(email)
Dictionary<string, string> userEmails = new Dictionary<string, string>();
// 或者用var更简洁
var userEmails = new Dictionary<string, string>();
为啥必须写两个类型?
因为C#是强类型语言,字典得知道你给它准备了什么类型的key和value。
添加元素
要加新的“键-值”对,用Add方法。key必须唯一!
userEmails.Add("user1", "user1@example.com");
userEmails.Add("user2", "user2@gmail.com");
如果你用同一个key再加一次,字典会生气,直接抛异常。
通过key访问value
字典最酷的地方就是可以直接通过key拿到value:
string email = userEmails["user1"];
Console.WriteLine(email); // user1@example.com
如果你用不存在的key,程序会立刻报错(抛KeyNotFoundException异常)。后面我们会学怎么安全地检查key。
通过key修改value
如果key已经有了,直接赋新值就行:
userEmails["user1"] = "user1@newmail.ru"; // 现在Vasya的email变了
如果key原来没有,这样赋值会新建一个元素。
用户示例
我们来升级一下我们的学习小程序。之前我们有个任务列表(List<string> tasks;)做ToDo程序。现在想加个“登录”功能:每个用户要有email。
可以这样写:
// UserId是string,Email也是string
var users = new Dictionary<string, string>();
users.Add("admin", "admin@myapp.com");
users.Add("alice", "alice@wonderland.com");
users.Add("bob", "bob@builder.com");
现在你随时可以通过登录名快速查到用户的email:
Console.WriteLine(users["alice"]); // => alice@wonderland.com
3. Dictionary的常用方法和属性
| 方法/属性 | 说明 |
|---|---|
|
添加新的“键-值”对。 |
|
通过key删除元素。 |
|
检查有没有这个key。 |
|
检查有没有这个value(很慢!)。 |
|
安全地通过key获取value,不会抛异常。 |
|
字典里“键-值”对的数量。 |
|
所有key的集合。 |
|
所有value的集合。 |
检查key是否存在
最常见(也最安全)的做法——先检查key在不在:
if (users.ContainsKey("user3"))
{
Console.WriteLine(users["user3"]);
}
else
{
Console.WriteLine("用户user3没找到!");
}
更安全的方式:TryGetValue
TryGetValue方法能避免异常:
if (users.TryGetValue("bob", out string email))
{
Console.WriteLine($"Bob的邮箱:{email}");
}
else
{
Console.WriteLine("Bob没找到!");
}
这是个好习惯,面试也常考——早点学会!而且这个方法比ContainsKey+索引快。
4. 遍历字典:foreach循环
如果你要遍历所有“键-值”对,用foreach循环。字典里的每个元素其实是KeyValuePair<TKey, TValue>对象:
foreach (var pair in users)
{
Console.WriteLine($"登录名: {pair.Key}, Email: {pair.Value}");
}
或者你想更潮一点:
foreach (var (login, email) in users)
{
Console.WriteLine($"{login}: {email}");
}
// 这种写法是元组解构(C# 7+)带来的。
5. 删除和修改value
要通过登录名删除用户很简单:
users.Remove("alice");
如果没有这个key,会返回false。放心删,不会抛异常。
修改用户的email:
users["bob"] = "bob@constructor.com";
如果key原来没有,会新加一对!
6. 字典的Keys和Values属性
如果你只想要所有登录名(key)或者所有email(value),用Keys和Values集合:
foreach (string login in users.Keys)
{
Console.WriteLine("登录名: " + login);
}
foreach (string email in users.Values)
{
Console.WriteLine("Email: " + email);
}
7. 字典的重要细节
key必须唯一
也就是说不能加两个一样的key。否则会抛异常。这样保证了数据安全:一个用户不能有两个email(每条记录只能有一个)。
key不能是null(对于string)
如果你用字符串key,试图加null会报错(ArgumentNullException)。如果你发现没key,可能是数据逻辑有问题。
为啥字典查找这么快?
Dictionary底层是哈希表。也就是说查找key不是一个个比,而是用特殊的“哈希函数”算出位置,几乎直接就能拿到value。
什么能当key用?
- 任何能正确判断相等、能生成唯一哈希码的类型(要有Equals和GetHashCode()方法)。
- 通常用string、int、Guid或者你自己的类型(但自定义类型要小心重写Equals/GetHashCode,不然会有奇怪bug)。
8. 把字典加到应用里
在我们的ToDo小程序里,我们加个用户字典,实现通过登录名查email并处理错误的功能:
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
// 用户字典:登录名 => email
var users = new Dictionary<string, string>
{
{ "admin", "admin@myapp.com" },
{ "alice", "alice@wonderland.com" },
{ "bob", "bob@builder.com" }
};
Console.WriteLine("请输入要查找email的用户登录名:");
string login = Console.ReadLine();
// 安全查找email
if (users.TryGetValue(login, out string email))
{
Console.WriteLine($"用户{login}的Email: {email}");
}
else
{
Console.WriteLine($"用户{login}没找到。");
}
// 遍历所有用户
Console.WriteLine("\n所有用户列表:");
foreach (var pair in users)
{
Console.WriteLine($"{pair.Key} => {pair.Value}");
}
}
}
9. 新手常见错误和坑
有时候很想这样写:
// 以为key不存在也没事
string value = users["nonexistent"]; // 崩了!KeyNotFoundException!
记住:如果不确定key一定存在,先用ContainsKey或TryGetValue检查。
还有,遍历value不能保证它们唯一!两个登录名可能有同一个email(如果你没控制好value的唯一性)。
方法也容易搞混——比如想通过value删:
users.Remove("bob@builder.com"); // 删不掉!它要的是key,不是value。
GO TO FULL VERSION