Dictionaries
A Dictionary<TKey, TValue> stores pairs of keys and values and finds any value by its key almost instantly, however many entries there are. Use one whenever you look things up by name or ID: prices by product code, users by email, word counts by word.
Creating and using a dictionary
var capitals = new Dictionary<string, string> { ["France"] = "Paris", ["Japan"] = "Tokyo", ["Kenya"] = "Nairobi", }; capitals["Brazil"] = "Brasília"; // add a new key capitals["Japan"] = "Tōkyō"; // replace an existing value Console.WriteLine(capitals["France"]); Console.WriteLine(capitals["Japan"]); Console.WriteLine(capitals.Count);
Paris Tōkyō 4
The indexer [key] = value adds the pair if the key is new and overwrites it otherwise. Keys are unique; values don't have to be.
Missing keys
Reading a key that doesn't exist throws a KeyNotFoundException:
var stock = new Dictionary<string, int> { ["apples"] = 12 }; Console.WriteLine(stock["pears"]);
Unhandled exception. System.Collections.Generic.KeyNotFoundException: The given key 'pears' was not present in the dictionary.
When a key might be missing, use TryGetValue, ContainsKey or GetValueOrDefault:
var stock = new Dictionary<string, int> { ["apples"] = 12, ["bananas"] = 0 }; if (stock.TryGetValue("apples", out int apples)) Console.WriteLine($"apples: {apples}"); Console.WriteLine(stock.ContainsKey("pears")); Console.WriteLine(stock.GetValueOrDefault("pears")); Console.WriteLine(stock.GetValueOrDefault("pears", -1));
apples: 12 False 0 -1
TryGetValue is the most efficient: it looks the key up once and gives you the value. ContainsKey followed by dict[key] looks it up twice.
Add, TryAdd and Remove
Add throws if the key is already present, which is useful when a duplicate means a bug. TryAdd returns false instead:
var users = new Dictionary<int, string>(); users.Add(101, "maria"); users.Add(102, "tom"); Console.WriteLine(users.TryAdd(101, "someone else")); Console.WriteLine(users[101]); Console.WriteLine(users.Remove(102)); Console.WriteLine(users.Remove(999)); Console.WriteLine(users.Count); users.Add(101, "duplicate");
False maria True False 1 Unhandled exception. System.ArgumentException: An item with the same key has already been added. Key: 101
Looping over a dictionary
Each entry is a KeyValuePair<TKey, TValue>, which you can deconstruct straight into two variables. Keys and Values give you just one side:
var prices = new Dictionary<string, decimal> { ["coffee"] = 3.20m, ["tea"] = 2.50m, ["cake"] = 4.75m, }; foreach (KeyValuePair<string, decimal> entry in prices) { Console.WriteLine($"{entry.Key}: {entry.Value:C}"); } foreach (var (item, price) in prices) { Console.WriteLine($"{item.ToUpper()} {price}"); } Console.WriteLine(string.Join(", ", prices.Keys)); Console.WriteLine(prices.Values.Sum());
coffee: $3.20 tea: $2.50 cake: $4.75 COFFEE 3.20 TEA 2.50 CAKE 4.75 coffee, tea, cake 10.45
A Dictionary doesn't promise any particular order. In practice you often get insertion order when you only add, but don't rely on it. Use SortedDictionary to keep keys sorted, or sort with LINQ when printing.
Counting things
Counting occurrences is one of the most common dictionary jobs:
string text = "the cat sat on the mat the end"; var counts = new Dictionary<string, int>(); foreach (string word in text.Split(' ')) { counts[word] = counts.GetValueOrDefault(word) + 1; } foreach (var (word, n) in counts.OrderByDescending(p => p.Value).ThenBy(p => p.Key)) { Console.WriteLine($"{word,-4} {new string('#', n)} {n}"); }
the ### 3 cat # 1 end # 1 mat # 1 on # 1 sat # 1
For simple counts, LINQ's CountBy does it in one call: text.Split(' ').CountBy(w => w).
Grouping into lists
Values can be any type, including collections. Here each first letter maps to a list of names:
string[] names = ["Amir", "Bea", "Alex", "Carla", "Ben", "Ava"]; var byLetter = new Dictionary<char, List<string>>(); foreach (string name in names) { char letter = name[0]; if (!byLetter.TryGetValue(letter, out List<string>? group)) { group = []; byLetter[letter] = group; } group.Add(name); } foreach (var (letter, group) in byLetter) { Console.WriteLine($"{letter}: {string.Join(", ", group)}"); }
A: Amir, Alex, Ava B: Bea, Ben C: Carla
Case-insensitive keys
String keys are case-sensitive by default. Pass a StringComparer when creating the dictionary to change that:
var headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) { ["Content-Type"] = "application/json", }; Console.WriteLine(headers["content-type"]); Console.WriteLine(headers.ContainsKey("CONTENT-TYPE"));
application/json True
Sorted dictionaries
SortedDictionary keeps its keys in order at all times. Lookups are a little slower than Dictionary, so use it only when you need the ordering:
var scores = new SortedDictionary<string, int> { ["zoe"] = 81, ["adam"] = 92, ["mia"] = 77, }; scores["bob"] = 64; foreach (var (name, score) in scores) { Console.WriteLine($"{name}: {score}"); }
adam: 92 bob: 64 mia: 77 zoe: 81
Try it yourself
Track a shopping basket: add quantities for repeated items, then print the bill using a price list.
apple x3 1.20 milk x1 1.15 bread x1 2.10 cheese x1 (no price) Total: 4.45