Dictionary
Explore how to utilize the Dictionary<TKey, TValue> collection in C# for quick and efficient data retrieval using unique keys. Learn to create, add, update, and merge dictionaries, manage key-value pairs, and implement safe access methods like TryAdd and TryGetValue. Understand the advantages of dictionaries over lists for large datasets and get practical insights into handling real-world scenarios such as counting character occurrences and merging collections.
We'll cover the following...
We’ve explored collections like lists, stacks, and queues. While these are excellent for managing sequences of data, they rely on sequential ordering or numeric indices to retrieve items. But what if we need to look up a user’s profile using their email address, or find a product’s price using its barcode? Searching through a massive list item by item would be incredibly slow.
To solve this, .NET provides the dictionary. This collection is optimized for fast lookups using unique keys instead of numeric indices. Because dictionaries use hashing internally, retrieving a value by its key operates in
Dictionaries are key-value stores. These are mappings between keys and associated values. Think of a dictionary like a physical translation book where the words serve as unique keys and the translations serve as the values:
Keys in .NET dictionaries must be unique. However, multiple keys can map to the same value.
Syntax
Dictionaries in .NET are created by instantiating the Dictionary<TKey, TValue> class. We can use any type as a key and any type as a value:
// Keys are of string type, values are of string typeDictionary<string, string> countryCapitalMapping = [];
Line 2: We declare a dictionary mapping string keys to string values and initialize it using a modern collection expression
[].
Keys and values can be of different types:
// Keys are of string type, values are int typeDictionary<string, int> countryPopulationMapping = [];
Line 2: We declare a dictionary mapping string keys to integer values.
Basic operations
There are several Dictionary<TKey, TValue> methods that constitute its primary functionality:
Add(): This method adds a key and value pair to the dictionary.TryAdd(): This method attempts to add a key and value and returns a boolean result, indicating whether the operation was successful. We use this method when we are not sure if the key we want to add already exists in the dictionary.TryGetValue(): This method attempts to retrieve the value associated with a specific key in constanttime and assigns it to an outvariable. It returns a boolean that indicates whether the operation was successful.[]: This indexer is used to read or update a value associated with the key. In arrays, we provide an integer index to the indexer. In dictionaries, however, we provide a key.
Let us see all of these primary methods working together in a fully executable example:
Line 3: We initialize an empty dictionary to store country-capital mappings.
Lines 6–7: We use the
Addmethod to insert new key-value pairs. If we tried to add"United States"again usingAdd, it would throw an exception.Line 10: We use
TryAddto safely attempt adding"United States". Because the key exists, it returnsfalseinstead of crashing the program.Line 18: We use
TryGetValueto safely look up"Japan". It returnstrueand populates thejapanCapitalvariable. We use the nullablestring?type to prevent compiler warnings if the key is missing and a null value is returned.Line 30: We read a value directly using the bracket indexer syntax
[].Line 33: We use the indexer to overwrite the existing value for
"United States".Line 37: We use the indexer to add an entirely new key-value pair (
"France"). The indexer automatically handles both adding and updating.
Example
Let us create a program that counts a letter’s occurrences in a given text. We could use characters as keys and the count as values:
Read through the following code and run it.
using System.Collections.Generic;
Console.WriteLine("Enter some text to count its characters:");
string text = Console.ReadLine() ?? string.Empty;
Dictionary<char, int> characterCount = [];
foreach (char character in text)
{
// If it is a space, we can disregard it
if (char.IsWhiteSpace(character))
{
continue;
}
// The ContainsKey method checks if the dictionary contains a given key
if (characterCount.ContainsKey(character))
{
// If the key is there, we only need to increase the count
characterCount[character]++;
}
else
{
// If the key is not found, it is the first time we are seeing this character
characterCount.Add(character, 1);
}
}
// The Keys property contains all the keys in the dictionary
foreach (char character in characterCount.Keys)
{
Console.WriteLine($"{character} --> {characterCount[character]}");
}Line 1: We include the necessary namespaces for generic collections.
Line 3: We prompt the user to enter text so they know the program is waiting for input.
Line 4: We read input from the console and provide a fallback to an empty string to prevent null references.
Line 6: We initialize an empty dictionary to map characters to integers.
Line 8: We loop through each character in the input string.
Lines 12–15: We use the
char.IsWhiteSpacemethod to ignore spaces.Lines 18–27: We use the
ContainsKeymethod to verify if the character is already tracked. If it is, we increment its value; otherwise, we add it with a starting value of1.Lines 31–34: We iterate over the
Keyscollection to print each character and its associated frequency.
Although the output displays the characters in the order they were first encountered, standard dictionaries do not officially guarantee a predictable sorting order. If we were to remove and add items, this apparent order would break.
How to merge two dictionaries
Sometimes you need to combine the data from two separate dictionaries into a single collection. C# does not provide a built-in Merge() method for dictionaries, so you must choose an approach based on your specific needs and how you want to handle duplicate keys.
The Add method approach
The most straightforward way to merge dictionaries is to iterate through the second dictionary and insert its items into the first one using the Add() method. You must be careful with this approach. If the second dictionary contains a key that already exists in the first dictionary, the Add() method will throw an ArgumentException.
If you want to overwrite existing values instead of throwing an error, you should use the bracket indexer (dict1[key] = value) instead of the Add() method.
The Concat method approach
If you prefer functional programming patterns, you can use the Concat() method from the System.Linq namespace. This method stitches two collections together without modifying the original dictionaries. Because Concat() returns a generic IEnumerable<KeyValuePair<TKey, TValue>>, you must call ToDictionary() on the result to convert it back into a usable dictionary.
Just like the Add() method, ToDictionary() will throw an exception if it encounters duplicate keys during the merge.
Note: We are using LINQ (System.Linq) to achieve this functional style. If this syntax looks unfamiliar, do not worry! We will formally introduce and explore LINQ in the next module.
Line 2: We import the
System.Linqnamespace to enable theConcatandToDictionaryextension methods.Lines 4–5: We initialize two separate dictionaries representing inventory for two stores.
Lines 8–11: We loop through
storeBand use theAddmethod to insert each key-value pair directly intostoreA.Lines 15–16: We initialize two entirely new dictionaries for the second example.
Lines 18–21: We use
Concatto mergestoreCandstoreDinto a single sequence, and then we useToDictionaryto generate a completely new dictionary from that combined sequence.Lines 13 and 23: We print the counts to verify that both merging techniques successfully combined the data