Dictionary<TKey, TValue> stores values under unique keys and finds a value by its key in roughly constant time, however many entries there are. It is the C# equivalent of a hash map: a phone book from name to number, a cache from ID to record, a count per word.
Creating a dictionary and reading values
Output:
12
2.50
2
True
False
Both initializer forms do the same thing. The ["key"] = value form (C# 6) uses the indexer, so a repeated key overwrites; the { key, value } form calls Add, so a repeated key throws when the line runs.
ContainsKey is a hash lookup and is fast. ContainsValue has to scan every entry, because values are not indexed.
Add vs the indexer vs TryAdd
There are three ways to put an entry in, and they differ only in what happens when the key already exists:
Output:
26
Caught ArgumentException
True
False
31
Add throwing on a duplicate is a feature: it surfaces data that should have been unique but was not. Use the indexer when "insert or update" is what you mean, and TryAdd (.NET Core 2.0 and later) when the first value should win.
Keys cannot be null. Add(null, ...) or dict[null] throws ArgumentNullException. Values can be null when the value type allows it.
KeyNotFoundException and TryGetValue
Reading a key that is not there with the indexer throws KeyNotFoundException. This is the most common dictionary error, and the fix is almost always TryGetValue.
Output:
Caught KeyNotFoundException
Found ana@example.com
Missing, value is null: True
no email
TryGetValue does one hash lookup and reports success as a bool. The pattern if (dict.ContainsKey(k)) { var v = dict[k]; } works but looks the key up twice. When the key is missing, the out variable gets the default value of the type (null, 0, false).
On .NET Core 2.0 and later there is also GetValueOrDefault(key, fallback), which returns the fallback when the key is missing: emails.GetValueOrDefault(103, "no email").
Updating and removing entries
Output:
2
True
False
1
0
cart["milk"] += 1 throws KeyNotFoundException if milk is not in the dictionary yet, because it reads before it writes. Remove returns false instead of throwing for a missing key, so there is no need to check ContainsKey first.
Iterating: KeyValuePair, Keys and Values
A foreach over a dictionary produces KeyValuePair<TKey, TValue> items, each with a Key and a Value.
Output:
Ana: 88
Ben: 72
Chloe: 95
Ana Ben Chloe
Total 255
77
pair.Value is read-only, so updating values means writing through the indexer. The last loop iterates over a List<string> copy of the keys, which is always safe; iterating scores.Keys directly while overwriting existing values is allowed on .NET Core 3.0 and later but throws InvalidOperationException on .NET Framework.
Adding a new key inside a foreach over the same dictionary throws InvalidOperationException on every version. Removing during enumeration throws on .NET Framework and is allowed from .NET Core 3.0. Code that must run everywhere collects the keys to remove first, then removes them after the loop.
With C# 7 and .NET Core 2.0 or later, KeyValuePair can be deconstructed in the loop header:
foreach (var (name, score) in scores)
{
Console.WriteLine($"{name}: {score}");
}
Counting with a dictionary
Counting occurrences is the textbook use. Read the current count with TryGetValue (a missing key gives 0), then write back.
Output:
the 3
cat 1
and 2
dog 1
bird 1
The same shape groups items: Dictionary<string, List<Order>>, where you fetch the list with TryGetValue, create and store one if missing, then Add to it. For one-off counting and grouping, LINQ does it in one expression: words.GroupBy(w => w).ToDictionary(g => g.Key, g => g.Count()). See LINQ.
Case-insensitive keys with a comparer
String keys compare exactly by default: "Apple" and "apple" are two keys. Pass an IEqualityComparer<string> to the constructor to change that.
Output:
False
text/html
1
StringComparer.OrdinalIgnoreCase is the right choice for identifiers such as HTTP headers, file extensions and usernames. Calling .ToLower() on every key before storing it also works, but it is easy to forget in one place.
For keys of your own class, the dictionary calls the key's GetHashCode and Equals. A class that does not override them compares by reference, so two separate objects with the same fields are different keys. See HashSet for how to write that pair.
Order, sorting and SortedDictionary
A Dictionary makes no promise about enumeration order. In practice a dictionary that has only had entries added enumerates in insertion order, but after a Remove, a later Add can reuse the freed slot and appear earlier. Code should never rely on it.
When you need an order, sort at the point of use or use a sorted collection:
Output:
Cairo 210
Lima 340
Oslo 520
By value, highest first:
Oslo 520
Lima 340
Cairo 210
Berlin, Cairo, Lima, Oslo
SortedDictionary<TKey, TValue> keeps its keys sorted at all times (it is a balanced tree), so lookups and inserts are O(log n) instead of O(1). Use it when you enumerate in key order often; sort a normal dictionary with LINQ when you only need order once. SortedList<TKey, TValue> is a third option that uses less memory but is slow to insert into when large.
Quick reference
| Task | Code |
|---|---|
| Create | new Dictionary<string, int>() |
| Insert or overwrite | d[k] = v |
| Insert, throw on duplicate | d.Add(k, v) |
| Insert only if new | d.TryAdd(k, v) |
| Read, throw if missing | d[k] |
| Read safely | d.TryGetValue(k, out var v) |
| Key exists | d.ContainsKey(k) |
| Remove | d.Remove(k) (returns bool) |
| Size | d.Count |
| Keys, values | d.Keys, d.Values |
| Sorted by key | d.OrderBy(p => p.Key) or SortedDictionary |
| Ignore case | new Dictionary<string, T>(StringComparer.OrdinalIgnoreCase) |
Common mistakes
- Reading a missing key with
d[k]. ThrowsKeyNotFoundException; useTryGetValue. - Calling
Addfor a key that may exist. ThrowsArgumentException; use the indexer orTryAdd. - Adding keys inside
foreachover the dictionary. ThrowsInvalidOperationException; collect changes and apply them after. - Relying on enumeration order. Sort, or use
SortedDictionary. - Changing a key object's fields after inserting it. Its hash code changes and the entry can no longer be found.
ContainsKeythen indexer. Two lookups;TryGetValuedoes one.
Frequently Asked Questions
What is the difference between Dictionary.Add and the indexer in C#?
dict.Add(key, value) inserts a new entry and throws ArgumentException if the key already exists. dict[key] = value inserts the entry if the key is new and overwrites the value if it exists, and never throws for a duplicate. TryAdd(key, value) inserts only when the key is new and returns false otherwise.
How does TryGetValue work in C#?
dict.TryGetValue(key, out var value) returns true and sets value when the key exists, and returns false and sets value to the default of its type when it does not. It does one lookup, where ContainsKey followed by dict[key] does two, and it never throws KeyNotFoundException.
How do I iterate over a Dictionary in C#?
foreach (KeyValuePair<string, int> pair in dict) gives each entry with pair.Key and pair.Value. To loop over only keys or only values, use dict.Keys or dict.Values. Do not add keys to the dictionary inside that loop: it throws InvalidOperationException.
Is a C# Dictionary ordered?
No order is guaranteed. A dictionary that only ever had entries added usually enumerates in insertion order, but after a Remove new entries can fill the freed slot, so the order changes. Sort when you need an order: dict.OrderBy(p => p.Key), or use SortedDictionary<TKey, TValue>, which always enumerates by key.
How do I make Dictionary keys case-insensitive?
Pass a comparer to the constructor: new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase). Then "Apple" and "apple" are the same key for lookups, Add and ContainsKey. The comparer is fixed when the dictionary is created.
What is a KeyValuePair in C#?
KeyValuePair<TKey, TValue> is the struct a dictionary hands you for each entry when you enumerate it. It has read-only Key and Value properties, so you cannot change an entry through it; write dict[pair.Key] = newValue instead (after the loop, or over a copy of the keys).