Search⌘ K
AI Features

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’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 O(1)O(1) (constant time). This means that whether the dictionary contains ten items or a million items, it takes roughly the same microscopic amount of time to find the value. This makes dictionaries vastly superior to lists for large dataset lookups.

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:

An example of a dictionary
An example of a dictionary

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 type
Dictionary<string, string> countryCapitalMapping = [];
Declaring a dictionary
  • 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 type
Dictionary<string, int> countryPopulationMapping = [];
Declaring a dictionary with mixed types
  • 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 constant O(1)O(1) time and assigns it to an out variable. 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:

C# 14.0
using System.Collections.Generic;
Dictionary<string, string> countryCapitalMapping = [];
// 1. Using the Add method
countryCapitalMapping.Add("United States", "Washington, D.C.");
countryCapitalMapping.Add("Japan", "Tokyo");
// 2. Using the TryAdd method
bool added = countryCapitalMapping.TryAdd("United States", "New York");
if (!added)
{
Console.WriteLine("TryAdd: The key 'United States' already exists!");
}
// 3. Using the TryGetValue method
// Note the string? type, which handles potential null values if the key is missing
bool retrieved = countryCapitalMapping.TryGetValue("Japan", out string? japanCapital);
if (retrieved)
{
Console.WriteLine($"TryGetValue: The capital of Japan is {japanCapital}.");
}
else
{
Console.WriteLine("TryGetValue: The key does not exist!");
}
// 4. Using the indexer []
// Reading with the indexer
Console.WriteLine($"Indexer (Read): US Capital is {countryCapitalMapping["United States"]}");
// Updating with the indexer
countryCapitalMapping["United States"] = "Washington, DC";
Console.WriteLine($"Indexer (Update): US Capital changed to {countryCapitalMapping["United States"]}");
// Adding a new pair with the indexer
countryCapitalMapping["France"] = "Paris";
Console.WriteLine($"Indexer (Add): Added France with capital {countryCapitalMapping["France"]}");
  • Line 3: We initialize an empty dictionary to store country-capital mappings.

  • Lines 6–7: We use the Add method to insert new key-value pairs. If we tried to add "United States" again using Add, it would throw an exception.

  • Line 10: We use TryAdd to safely attempt adding "United States". Because the key exists, it returns false instead of crashing the program.

  • Line 18: We use TryGetValue to safely look up "Japan". It returns true and populates the japanCapital variable. We use the nullable string? 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:

An example of a dictionary
An example of a dictionary

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]}");
}
Counting character occurrences
  • 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.IsWhiteSpace method to ignore spaces.

  • Lines 18–27: We use the ContainsKey method to verify if the character is already tracked. If it is, we increment its value; otherwise, we add it with a starting value of 1.

  • Lines 31–34: We iterate over the Keys collection 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.

C# 14.0
using System.Collections.Generic;
using System.Linq;
Dictionary<string, int> storeA = new() { ["Apples"] = 50 };
Dictionary<string, int> storeB = new() { ["Oranges"] = 30 };
// 1. Merging using the Add method (modifies storeA)
foreach (KeyValuePair<string, int> item in storeB)
{
storeA.Add(item.Key, item.Value);
}
Console.WriteLine($"Store A total items: {storeA.Count}"); // Output: 2
Dictionary<string, int> storeC = new() { ["Bananas"] = 20 };
Dictionary<string, int> storeD = new() { ["Grapes"] = 40 };
// 2. Merging using LINQ Concat (creates a brand new dictionary)
Dictionary<string, int> combinedStore = storeC
.Concat(storeD)
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
Console.WriteLine($"Combined store total items: {combinedStore.Count}"); // Output: 2
  • Line 2: We import the System.Linq namespace to enable the Concat and ToDictionary extension methods.

  • Lines 4–5: We initialize two separate dictionaries representing inventory for two stores.

  • Lines 8–11: We loop through storeB and use the Add method to insert each key-value pair directly into storeA.

  • Lines 15–16: We initialize two entirely new dictionaries for the second example.

  • Lines 18–21: We use Concat to merge storeC and storeD into a single sequence, and then we use ToDictionary to 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