iT邦幫忙

2026 iThome 鐵人賽

DAY 17
0
Modern Web

現在就學C# 與 ASP.NET Core系列 第 17 篇

Day 17|Dictionary 與 HashSet:用 Key 找資料、避免資料重複

  • 分享至 

  • xImage
  •  

Day 16 我們學過:

List<Product> products =
    new List<Product>();

List<T> 適合:

管理一組有順序的資料
→ 可以新增、刪除
→ 可以使用 Index

但今天來看兩個不同的需求。


問題一:我想透過 ID 找到資料

例如:

P001 → Keyboard
P002 → Mouse
P003 → Monitor

我們希望:

P002
 ↓
找到 Mouse

這時可以使用:

Dictionary<TKey, TValue>

問題二:我不希望資料重複

例如已使用的優惠碼:

WELCOME
SUMMER
VIP

如果再次加入:

WELCOME

我們希望:

WELCOME
→ 只保存一次

這時可以使用:

HashSet<T>

所以今天最重要的兩個 Mental Model:

Dictionary<TKey, TValue>
→ Key → Value

HashSet<T>
→ 不重複

1. Dictionary<TKey, TValue> 是什麼?

Dictionary 可以先理解成:

透過一個 Key,找到對應的 Value。

例如:

P001 → Keyboard
P002 → Mouse
P003 → Monitor

其中:

P001
P002
P003
→ Key

而:

Keyboard
Mouse
Monitor
→ Value

最重要的關係:

Key
 ↓
找到
 ↓
Value

例如:

P002
 ↓
Mouse

這就是 Dictionary 的核心。


2. TKey 和 TValue 是什麼?

先看:

Dictionary<string, string>

第一個 Type:

string
→ TKey

第二個 Type:

string
→ TValue

代表:

Key
→ string

Value
→ string

例如:

"P001" → "Keyboard"
"P002" → "Mouse"

也可以:

Dictionary<int, string>

代表:

Key
→ int

Value
→ string

例如:

1 → Amy
2 → Bob
3 → Kevin

所以:

Dictionary<TKey, TValue>

TKey
→ Key 的 Type

TValue
→ Value 的 Type

3. 建立 Dictionary 與 Add()

建立:

Dictionary<string, string> products =
    new Dictionary<string, string>();

代表:

products

Key
→ string

Value
→ string

一開始:

Count
→ 0

接著加入資料:

products.Add(
    "P001",
    "Keyboard"
);

products.Add(
    "P002",
    "Mouse"
);

products.Add(
    "P003",
    "Monitor"
);

現在:

P001 → Keyboard
P002 → Mouse
P003 → Monitor

Add() 需要:

Key
+
Value

例如:

products.Add(
    "P001",
    "Keyboard"
);

就是:

P001
→ Key

Keyboard
→ Value

4. Dictionary 的 Key 必須唯一

Dictionary 有一個重要規則:

同一個 Dictionary 裡,Key 不能重複。

假設已經有:

products.Add(
    "P001",
    "Keyboard"
);

又執行:

products.Add(
    "P001",
    "Mouse"
);

就會發生錯誤。

因為:

P001 → Keyboard
P001 → Mouse
↑
相同 Key

如果 Key 可以重複,就無法確定:

P001
→ 到底要找到哪一筆?

所以:

Dictionary
→ Key 必須唯一

但是 Value 可以重複。

例如:

001 → Amy
002 → Amy

沒有問題。


5. 使用 Key 取得與修改 Value

Day 16 的 List 使用:

names[0]

透過 Index 取得資料。

Dictionary 則是:

products["P001"]

透過 Key 取得 Value。

例如:

string productName =
    products["P001"];

Console.WriteLine(productName);

結果:

Keyboard

所以:

List<T>
→ [index]

Dictionary<TKey, TValue>
→ [key]

修改 Value

原本:

P001 → Keyboard

可以:

products["P001"] =
    "Mechanical Keyboard";

變成:

P001
→ Mechanical Keyboard

所以:

dictionary[key]
→ 取得 Value

dictionary[key] = value
→ 修改 Value

Key 不存在時

例如:

products["P004"] =
    "Webcam";

如果 P004 原本不存在,這行也會新增:

P004 → Webcam

6. TryGetValue():Key 不存在怎麼辦?

假設 Dictionary 只有:

P001
P002
P003

如果直接:

products["P999"]

會發生錯誤。

因為:

P999
→ 不存在

如果不確定 Key 是否存在,可以使用:

TryGetValue()

例如:

if (
    products.TryGetValue(
        "P002",
        out var productName
    )
)
{
    Console.WriteLine(productName);
}

結果:

Mouse

可以理解成:

TryGetValue("P002", ...)
        ↓
嘗試用 P002 找 Value
        ↓
找到了?

如果找到:

true
+
Value 放進 productName

如果找不到:

false

所以:

TryGetValue()

找到
→ true
→ 取得 Value

找不到
→ false

out var productName 是什麼?

這裡:

out var productName

可以先理解成:

如果找到 Value,就把找到的資料放進 productName。

例如:

P002
 ↓
Mouse
 ↓
productName

這篇先知道用途即可,out 的完整用法之後再深入。


補充|ContainsKey()

如果只想知道:

這個 Key 存不存在?

可以:

bool exists =
    products.ContainsKey("P001");

整理:

ContainsKey()
→ 只確認 Key 是否存在

TryGetValue()
→ 確認是否存在
→ 同時取得 Value

如果本來就需要 Value,通常直接使用 TryGetValue() 會更方便。


7. Remove() 與 foreach

Remove():根據 Key 刪除

假設:

P001 → Keyboard
P002 → Mouse
P003 → Monitor

執行:

products.Remove("P002");

結果:

P001 → Keyboard
P003 → Monitor

所以:

Dictionary.Remove(key)
→ 根據 Key 刪除資料

foreach:走訪 Dictionary

Dictionary 一樣可以使用 foreach:

foreach (var item in products)
{
    Console.WriteLine(
        $"{item.Key} - {item.Value}"
    );
}

結果:

P001 - Keyboard
P002 - Mouse
P003 - Monitor

每一筆資料都有:

item.Key
item.Value

可以想成:

item
├── Key
└── Value

所以:

foreach
→ 一筆一筆取得 Key / Value

8. Dictionary<string, Product>:透過 ID 找 Object

實務上,Value 不一定只是 string。

也可以是一個 Object。

例如:

class Product
{
    public string Name { get; private set; }

    public decimal Price { get; private set; }

    public Product(
        string name,
        decimal price)
    {
        Name = name;
        Price = price;
    }
}

建立:

Dictionary<string, Product> products =
    new Dictionary<string, Product>();

代表:

Key
→ string

Value
→ Product

加入:

products.Add(
    "P001",
    new Product(
        "Keyboard",
        1500
    )
);

products.Add(
    "P002",
    new Product(
        "Mouse",
        800
    )
);

products.Add(
    "P003",
    new Product(
        "Monitor",
        6000
    )
);

可以想成:

P001
 ↓
Product
├── Name = Keyboard
└── Price = 1500

P002
 ↓
Product
├── Name = Mouse
└── Price = 800

P003
 ↓
Product
├── Name = Monitor
└── Price = 6000

使用商品 ID 找 Product

假設:

string productId = "P002";

使用:

if (
    products.TryGetValue(
        productId,
        out var product
    )
)
{
    Console.WriteLine(
        $"{product.Name} - ${product.Price}"
    );
}
else
{
    Console.WriteLine("找不到商品");
}

結果:

Mouse - $800

完整流程:

P002
 ↓
Dictionary
 ↓
TryGetValue()
 ↓
找到 Product Object
 ↓
使用 Name / Price

這就是 Dictionary 很常見的使用方式:

ProductId → Product
UserId → User
AccountId → Account

9. HashSet<T>:管理不重複資料

接著看另一個需求。

假設我們要保存已使用的優惠碼:

WELCOME
SUMMER
VIP

如果再次加入:

WELCOME

需求是:

WELCOME
→ 只保存一次

這時可以使用:

HashSet<string>

HashSet<T> 可以先理解成:

用來管理不重複元素的 Collection。

但這裡要注意:

HashSet 並不是只能保存 string。

HashSet<T> 裡面的:

T

和 List<T> 一樣,代表:

要保存的元素 Type

所以可以:

HashSet<string>
HashSet<int>
HashSet<Product>
HashSet<Student>

例如:

HashSet<string>
→ 不重複的 string

HashSet<int>
→ 不重複的 int

HashSet<Product>
→ 不重複的 Product Object

10. HashSet 的資料「長什麼樣」?

先來比較三種 Collection。

List

List<Product>

[0] Product
[1] Product
[2] Product

List 的重點:

順序
+
Index

Dictionary

Dictionary<string, Product>

P001 → Product
P002 → Product
P003 → Product

Dictionary 的重點:

Key
 ↓
Value

HashSet

HashSet<Product>

Product
Product
Product

HashSet 沒有:

Index

也沒有:

Key → Value

每一筆就是一個元素。

可以想成:

HashSet<T>
│
├── T
├── T
└── T

它真正關心的是:

這個元素
→ 是否已經存在?

所以 HashSet 的核心不是:

它排在哪裡?

也不是:

它的 Key 是什麼?

而是:

這筆資料是否重複?

11. HashSet<string>:保存不重複的字串

建立:

HashSet<string> codes =
    new HashSet<string>();

加入:

codes.Add("WELCOME");
codes.Add("SUMMER");
codes.Add("VIP");

可以想成:

HashSet<string>
│
├── WELCOME
├── SUMMER
└── VIP

如果再次:

codes.Add("WELCOME");

不會變成:

WELCOME
WELCOME

最後仍然只有一份:

WELCOME

所以:

HashSet<string>
→ 每個 string 不重複

12. HashSet 也可以保存 int

例如:

HashSet<int> numbers =
    new HashSet<int>();

numbers.Add(10);
numbers.Add(20);
numbers.Add(10);
numbers.Add(30);

概念上最後是:

HashSet<int>

10
20
30

第二個:

10

因為已經存在,所以不會再加入。

所以:

HashSet<int>
→ 每個 int 只保存一次

13. HashSet 也可以保存 Object

同樣也可以:

HashSet<Product> products =
    new HashSet<Product>();

例如:

Product keyboard =
    new Product(
        "Keyboard",
        1500
    );

Product mouse =
    new Product(
        "Mouse",
        800
    );

products.Add(keyboard);
products.Add(mouse);

概念上:

HashSet<Product>
│
├── Product
│   ├── Name = Keyboard
│   └── Price = 1500
│
└── Product
    ├── Name = Mouse
    └── Price = 800

所以:

HashSet 不只可以保存基本資料,也可以保存自己建立的 Object。


14. Object 的「不重複」比較特別

這裡有一個很重要的觀念。

假設:

Product product1 =
    new Product(
        "Keyboard",
        1500
    );

Product product2 =
    new Product(
        "Keyboard",
        1500
    );

從資料內容來看:

product1

Name = Keyboard
Price = 1500

和:

product2

Name = Keyboard
Price = 1500

看起來完全一樣。

但它們是分別建立出來的兩個 Object:

product1
→ Object A

product2
→ Object B

如果:

HashSet<Product> products =
    new HashSet<Product>();

products.Add(product1);
products.Add(product2);

初學者可能會想:

Name 一樣
Price 一樣

→ 應該算重複?

但事情沒有這麼單純。

HashSet 必須先知道:

什麼情況下,兩個 Product 才算「相同」?

例如到底是:

Name 一樣
→ 就算相同?

還是:

Name + Price 都一樣
→ 才算相同?

或者:

ProductId 一樣
→ 才算同一個 Product?

這其實是:

Equality
→ 如何判斷兩個 Object 是否相同

的問題。

因此這篇先使用:

HashSet<string>
HashSet<int>

來學習 HashSet。

因為:

"HELLO" 和 "HELLO"
→ 是否相同很直覺

10 和 10
→ 是否相同也很直覺

而:

Product Object
和
Product Object

要怎麼判斷相同,需要額外定義規則。

這部分之後再深入。


15. HashSet 到底怎麼避免重複?

HashSet 名字裡有:

Hash

所以它並不是每次都單純從第一筆開始一筆一筆比較。

初學階段可以先用這個簡化模型理解:

加入資料
   ↓
計算 Hash
   ↓
找到資料可能所在的位置
   ↓
確認是否已有相同資料
   ↓
不存在
→ 加入

已存在
→ 不加入

例如:

codes.Add("WELCOME");

第一次:

WELCOME
   ↓
Hash
   ↓
檢查
   ↓
不存在
   ↓
加入成功

再次:

codes.Add("WELCOME");

可以理解成:

WELCOME
   ↓
Hash
   ↓
找到可能位置
   ↓
確認已存在
   ↓
不加入

這也是為什麼:

HashSet<T>

非常適合:

判斷資料是否已存在
+
避免保存重複元素

這篇先理解到這裡即可。

Hash、Collision、GetHashCode() 等底層細節之後再介紹。


16. Add() 會回傳 bool

HashSet 的 Add() 還有一個很好用的特性。

它會告訴你:

這次是否真的加入成功。

例如:

HashSet<string> codes =
    new HashSet<string>();

bool added =
    codes.Add("WELCOME");

第一次加入:

added
→ true

如果再次:

bool addedAgain =
    codes.Add("WELCOME");

結果:

addedAgain
→ false

因為:

WELCOME
→ 已經存在

所以:

HashSet.Add()

true
→ 原本不存在
→ 成功加入

false
→ 原本已存在
→ 沒有再次加入

17. Contains()、Remove()、foreach

Contains()

判斷資料是否存在:

bool exists =
    codes.Contains("WELCOME");

所以:

Contains()
→ 這個元素存在嗎?

Remove()

刪除:

codes.Remove("WELCOME");

所以:

Remove()
→ 刪除指定元素

foreach

走訪:

foreach (string code in codes)
{
    Console.WriteLine(code);
}

但有一個重要觀念:

不要依賴 HashSet<T> 的元素順序。

HashSet 的主要目的不是:

維持第 1 筆
第 2 筆
第 3 筆

而是:

確保元素不重複

因此:

需要順序
→ List<T>

需要不重複
→ HashSet<T>

18. List、Dictionary、HashSet 怎麼選?

現在已經學過三種 Collection。

Collection 適合的需求 核心概念
List<T> 管理有順序的一組資料 順序 / Index
Dictionary<TKey, TValue> 透過 Key 找資料 Key → Value
HashSet<T> 管理不重複資料 Unique

最簡單的判斷:

需要一組有順序的資料?
→ List<T>

需要透過 Key 找資料?
→ Dictionary<TKey, TValue>

需要資料不能重複?
→ HashSet<T>

例如:

商品清單
→ List<Product>
商品 ID → Product
→ Dictionary<string, Product>
已使用優惠碼
→ HashSet<string>

也可以:

不重複的數字
→ HashSet<int>

甚至:

不重複的 Product
→ HashSet<Product>

只是自訂 Object 要先決定:

什麼情況下
→ 兩個 Object 算相同?

重點不是哪一個 Collection 比較好。

而是:

資料需求不同,選擇的 Collection 也不同。


Day 17 小結

今天最重要的是三個 Mental Model:

List<T>
→ 有順序的一組資料
Dictionary<TKey, TValue>
→ Key → Value
HashSet<T>
→ 一組不重複的 T

而:

T
→ 不只能是 string

例如:

HashSet<string>
→ 不重複的 string

HashSet<int>
→ 不重複的 int

HashSet<Product>
→ 不重複的 Product

只是使用自訂 Object 時,還需要進一步回答:

兩個 Object
→ 什麼情況算相同?

這就是之後會碰到的 Equality。

最後記住:

需要順序
→ List<T>

需要 Key 找資料
→ Dictionary<TKey, TValue>

需要避免重複
→ HashSet<T>

先看資料需求,再選擇適合的 Collection。


上一篇
Day 16|List<T>:管理會變動的一組資料
系列文
現在就學C# 與 ASP.NET Core 共 17 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言