C# Dictionary: Complete Guide [2022]
This article is a complete guide to the Dictionary data structure in C#.
C# Dictionary is a data structure that holds key-value pairs. It's called a Dictionary because the key is used to look up the corresponding value, just like in a real dictionary. The good thing is that the dictionary is a generic, so we can use it to store values of any type.
Dictionary usage
Initialization
To initialize a dictionary in C#, we first need to provide the type for the key and the type for the value.
For example, for a dictionary that stores the name and age of a person, the initialization would be:
Dictionary<string, int> nameToAge = new Dictionary<string, int>();
The first type inside the angle brackets is the key type. The second type is the value type.
Initial values
To define initial values at the time of initialization, we use the following syntax:
Dictionary<string, int> nameToAge = new Dictionary<string, int>()
{
{"John", 26},
{"Anna", 34 }
};
The collection-initializer syntax above is like the initialization of an array.
Another way to initialize a dictionary is object initializer introduced in C# 6:
Dictionary<string, int> nameToAge = new Dictionary<string, int>
{
["John"] = 26,
["Anna"] = 34
};
Adding item
To add an item to a dictionary, we use the Add method. For example:
nameToAge.Add("Adam", 24);
This would add "Adam" and his age of 24 to the nameToAge dictionary.
Remove item
To remove an item from a dictionary, we use the Remove method. For example:
nameToAge.Remove("Adam");
This would remove "Adam" from the nameToAge dictionary.
The remove method uses the key to determine which item to remove.
Update items
To update an item in a dictionary, we update the value associated with the key. For example:
nameToAge["Adam"] = 24;
This would update Adam's age of 24 to nameToAge dictionary.
Remove all values
To remove all the values from a dictionary, we use the Clear
method.
For example:
nameToAge.Clear();
This would clear nameToAge dictionary of all items.
Get the number of elements
To get the number of items in the dictionary, we use the Count property.
For example:
int count = nameToAge.Count;
This would get the number of items in nameToAge dictionary.
Duplicate values
In C#, a dictionary can only have one value for a key.
If we add another value with the same key, then we'll get the KeyNotFoundException exception.
For example, if we try to add a value with key "Adam" then we would get:
Dictionary<string, int> nameToAge = new ()
{
{"Adam", 26},
};
nameToAge.Add("Adam", 24);
Here we get the exception because there is already a key "Adam" and the dictionary cannot have two values for the same key.
As developers, we need to handle these exceptions by first checking for the key to exist before adding a value.
To check if a dictionary already has a value, we can use the ContainsKey
method:
nameToAge.ContainsKey("Adam")
It's a good design practice to use values we know are unique.
In the example above, it would be better to use a unique ID instead of a name.
TryGetValue
TryGetValue method is a safe way to get a value from a dictionary without having to handle exceptions.
It returns a bool value to let us know if the key was found.
For example, we can use TryGetValue this way:
int age = nameToAge.TryGetValue("Adam", out int a) ? a : default;
The code above will first check if the dictionary has a key "Adam" and if it does, returns its value. If it does not, then age will be set to the default value for int, which is 0.
The "Try" pattern is very common in C# and .NET, I explained it in detail in my article on TryParse.
Iterate over a Dictionary
To iterate over keys and values of a dictionary, we can use the foreach loop:
foreach (KeyValuePair<int, string> item in nameToAge)
{
item.Key;
item.Value;
}
The item variable is an object of type KeyValuePair<Tkey, TValue>
which has the properties to provide the key and value.
KeyValuePair is a very simple struct. It only holds two properties - Key and Value:
Get keys and values
Dictionary exposes two read-only properties to get all keys or values:
These properties are useful if we want to iterate over all keys or values of a dictionary.
Internals
C# dictionary uses a hash table as its internal data structure.
Hash tables rely on hash codes to locate an item.
For example, adding an item to a dictionary performs these 3 steps in the background:
- Compute a hash code for the key.
- Verify the key's hash code is not equal to the key already in the dictionary.
- Place the entry in the hash table bucket.
Read my article on Hashset to find out more about hash codes.
Use immutable objects for keys
Dictionary can break if we use a reference type as a key.
That's because Dictionary stores a reference to that object and not the object itself.
So if we change the object after adding it to the dictionary, then we would break the whole dictionary.
Because of that, it's a best practice to use immutable objects, like strings, when using the dictionary.
C# 9.0 in a Nutshell explains why:
If an object’s hashcode changes after it’s been added as a key to a dictionary, the object will no longer be accessible in the dictionary.
TryGetValue vs ContainsKey+Item
TryGetValue and ContainsKey are the two most popular ways of getting a value from a dictionary.
For ContainsKey, it's a two-step process:
- Do the lookup to verify the key exists in the dictionary.
- Do the lookup to get the value from the dictionary based on the key.
On the other hand, TryGetValue does the lookup and gets the value in one step.
Because of the performance, we should always use TryGetValue to get a value from a dictionary.
Dictionary exceptions
These are the most common exceptions we can get from a dictionary:
Exception type | Exception occurs |
---|---|
KeyNotFoundException | If there is no key with the value we are looking up. |
ArgumentOutOfRangeException | If the index that we are looking up is out of range. |
ArgumentNullException | If the key is null. |
ArgumentException | If we try to add a key that already exists. |
To prevent these exceptions, use TryGetValue and TryAdd.
Dictionary vs OrderedDictionary
The main difference between Dictionary and OrderedDictionary is that Dictionary does not guarantee the order in which it will return values, but OrderedDictionary does.
OrderedDictionary is less efficient than Dictionary because insert/delete operations have to be done on an array, with complexity of O(n) at worst.
Dictionary vs SortedDictionary
SortedDictionary is another class that implements IDictionary.
The main difference between a Dictionary and SortedDictionary is that SortedDictionary uses a binary search tree with O(log n) retrieval, while Dictionary is a hash table of O(1) complexity for access.
Use SortedDictionary if you have an extensive list of items and need them sorted.
ReadOnlyDictionary
ReadOnlyDictionary is an implementation of IDictionary that does not allow adding or deleting items.
It only provides read-access through its properties
ReadOnlyDictionary is a decorator wrapper around the regular Dictionary class.
Performance
The following table shows the complexity of each IDictionary implementation:
Implementation | Complexity |
---|---|
Dictionary, OrderedDictionary | O(1) |
SortedDictionary | O(log n) |
List | O(n) |
Historically, computer scientists have been exploring the most efficient way of searching through a dictionary. Richard Bornat from Middlesex University explained the problem here:
Concurrency
The Dictionary implementation is not thread-safe.
For example, one thread can change the data structure while another thread tries to iterate, causing an exception.
To make a thread-safe dictionary in C#, use the ConcurrentDictionary implementation.
All public and protected members of ConcurrentDictionary are thread-safe and we can use them concurrently from multiple threads.
The ConcurrentDictionary implementation uses tables to keep track of dictionary's state:
Summary
The Dictionary data structure in C# is a collection of key-value pairs.
It's called a Dictionary because the key is used to look up the corresponding value, just like in a real dictionary.
We can initialize the Dictionary using either a collection-initializer or an object initializer.
To manage a dictionary, we use:
- Add method
- Remove method
- Clear method
- Update the value associated with the key
Be careful and catch exceptions when using any of the methods that change the structure, such as Add, Remove, Clear, or Update.
The most common way to retrieve a value from a dictionary is using TryGetValue, which gets a value without throwing exceptions.
Besides the standard Dictionary, there are other implementation of IDictionary interface:
- The Dictionary class does not guarantee the order in which it will return values, but OrderedDictionary does.
- ReadOnlyDictionary does not allow adding or deleting items.
- ConcurrentDictionary uses tables to keep track of dictionary's state from multiple threads, making it thread-safe.
FAQ
What are valid C# dictionary keys?
In C#, all keys that match the type of the dictionary are valid.
The only catch to be aware of is that the key cannot be null.
If we try to use null as a key, then we'll get an ArgumentNullException exception:
System.ArgumentNullException: Value cannot be null. (Parameter 'key')
Can we have a dictionary inside a dictionary?
Dictionary can have a dictionary as its value.
For example:
Dictionary<string, Dictionary<int, string>> nameToAgeByBirthDate = new Dictionary<string, Dictionary<int, string>>();
Here nameToAgeByBirthDate has 2 key-value pairs.
Even though C# supports nested dictionaries, I prefer to avoid them.
It makes the code more complex with no benefit. List or array is always a better choice.
Can key and value be of the same type?
In C#, key and value of a dictionary can have the same type. For example, to create a string-to-string mapping, we can use a Dictionary<string, string>
.
- Published on
Related Posts
- C# Tuple: Complete Guide
- C# String Length
- REST API: PATCH vs. PUT
- String Enums in C#: Everything You Need to Know
- 10 Techniques for Estimating Software Projects
- Scrum for Software Developers: Why It's Worth Understanding
- How To Improve Cumulative Layout Shift Score
- How to Update npm Packages In 4 Easy Steps
- Dictionary usage
- Initialization
- Initial values
- Adding item
- Remove item
- Update items
- Remove all values
- Get the number of elements
- Duplicate values
- TryGetValue
- Iterate over a Dictionary
- Get keys and values
- Internals
- Use immutable objects for keys
- TryGetValue vs ContainsKey+Item
- Dictionary exceptions
- Dictionary vs OrderedDictionary
- Dictionary vs SortedDictionary
- ReadOnlyDictionary
- Performance
- Concurrency
- Summary
- FAQ