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

C#
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);
Output
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:

C#
var stock = new Dictionary<string, int> { ["apples"] = 12 };
Console.WriteLine(stock["pears"]);
Output
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:

C#
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));
Output
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:

C#
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");
Output
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:

C#
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());
Output
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:

C#
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}");
}
Output
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:

C#
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)}");
}
Output
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:

C#
var headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
    ["Content-Type"] = "application/json",
};

Console.WriteLine(headers["content-type"]);
Console.WriteLine(headers.ContainsKey("CONTENT-TYPE"));
Output
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:

C#
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}");
}
Output
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.

Try it yourself · Program.cs
Edit the code, copy it into Program.cs and run it with dotnet run Program.cs (.NET 10 or later). How to set up .NET
Output
apple   x3   1.20
milk    x1   1.15
bread   x1   2.10
cheese  x1  (no price)
Total: 4.45