DictionaryThe C# Dictionary is a collection that we can use to map keys to values. Each key must have the same type (like string), and all values must have a type as well.
For remembering things, the Dictionary is both fast and effective. We specify the key and value types. The Add method inserts into the Dictionary.
Here we add 4 keys (each with an int value) to 2 separate Dictionary instances. Every Dictionary has pairs of keys and values.
Dictionary is used with different elements. We specify its key type and its value type (string, int).Add() to set 4 keys to 4 values in a Dictionary. Then we access the Count property to see how many items we added.Dictionary with the same internal values as the first version, but its syntax is more graceful.using System; using System.Collections.Generic; // Version 1: create a Dictionary, then add 4 pairs to it. var dictionary = new Dictionary<string, int>(); dictionary.Add("cat", 2); dictionary.Add("dog", 1); dictionary.Add("llama", 0); dictionary.Add("iguana", -1); // The dictionary has 4 pairs. Console.WriteLine("DICTIONARY 1: " + dictionary.Count); // Version 2: create a Dictionary with an initializer. var dictionary2 = new Dictionary<string, int>() { {"cat", 2}, {"dog", 1}, {"llama", 0}, {"iguana", -1} }; // This dictionary has 4 pairs too. Console.WriteLine("DICTIONARY 2: " + dictionary2.Count);DICTIONARY 1: 4 DICTIONARY 2: 4
TryGetValueThis is often the most efficient lookup method. In my experience, most of the time we want to use TryGetValue—so learn how to use it right away when learning Dictionary.
TryGetValue, which tests for the key. It then returns the value if it finds the key.TryGetValue. Declare the local variable inside the if-condition.using System; using System.Collections.Generic; var values = new Dictionary<string, string>(); values.Add("A", "uppercase letter A"); values.Add("c", "lowercase letter C"); // Part 1: local variable result. string result; if (values.TryGetValue("c", out result)) { Console.WriteLine(result); } // Part 2: use inline "out string." if (values.TryGetValue("c", out string description)) { Console.WriteLine(description); }lowercase letter C lowercase letter C
Let us loop over the data in a Dictionary. With each KeyValuePair, there are Key and Value properties—this gives us all data stored.
Dictionary with string keys and int values. The Dictionary stores animal counts.foreach-loop, each KeyValuePair struct has 2 members, a string Key and an int Value.KeyValuePair directly, often we can just use var for simpler code.using System;
using System.Collections.Generic;
// Part 1: initialize data.
Dictionary<string, int> data = new Dictionary<string, int>()
{
{"cat", 2},
{"dog", 1},
{"llama", 0},
{"iguana", -1}
};
// Part 2: loop over pairs with foreach.
foreach (KeyValuePair<string, int> pair in data)
{
Console.WriteLine("FOREACH KEYVALUEPAIR: {0}, {1}", pair.Key, pair.Value);
}
// Part 3: use var keyword to enumerate Dictionary.
foreach (var pair in data)
{
Console.WriteLine("FOREACH VAR: {0}, {1}", pair.Key, pair.Value);
}FOREACH KEYVALUEPAIR: cat, 2
FOREACH KEYVALUEPAIR: dog, 1
FOREACH KEYVALUEPAIR: llama, 0
FOREACH KEYVALUEPAIR: iguana, -1
FOREACH VAR: cat, 2
FOREACH VAR: dog, 1
FOREACH VAR: llama, 0
FOREACH VAR: iguana, -1KeysThe Keys property returns a collection of type KeyCollection, not an actual List. We can convert it into a List. This property is helpful in many programs.
using System; using System.Collections.Generic; var data = new Dictionary<string, int>() { {"cat", 2}, {"dog", 1}, {"llama", 0}, {"iguana", -1} }; // Store keys in a List. var list = new List<string>(data.Keys); // Loop through the List. foreach (string key in list) { Console.WriteLine("KEY FROM LIST: " + key); }KEY FROM LIST: cat KEY FROM LIST: dog KEY FROM LIST: llama KEY FROM LIST: iguana
Count and RemoveCount returns the number of keys in a dictionary. And Remove eliminates a key (and its value) from the dictionary—if the key is found.
Count returns the total number of keys—this is the simplest way to count all pairs in a Dictionary.Remove will not cause an error if the key is not found, but if the key is null, it will cause an exception.using System;
using System.Collections.Generic;
var lunch = new Dictionary<string, int>() { {"carrot", 1}, {"pear", 4}, {"apple", 6}, {"kiwi", 3} };
Console.WriteLine("COUNT: " + lunch.Count);
// Remove pear.
lunch.Remove("pear");
Console.WriteLine("COUNT: " + lunch.Count);COUNT: 4
COUNT: 3GetValueOrDefaultThis method is available on Dictionary in .NET 5. It safely (with no possible exceptions) gets a value from the Dictionary, or the default value for the value's type.
null. The default can be determined with a default() call.GetValueOrDefault.int.using System;
using System.Collections.Generic;
var cats = new Dictionary<string, int>() { { "mittens", 5 } };
// Has value 5.
Console.WriteLine(cats.GetValueOrDefault("mittens"));
// Not found.
Console.WriteLine(cats.GetValueOrDefault("?"));5
0TryAddWith this method (part of .NET 5) we add an entry only if the key is not found. It returns a bool that tells us whether the key was added.
TryAdd, and the key is not found in the dictionary, so it is added with the value we specify (1).TryAdd again. But "test" already is present, so TryAdd returns false and the dictionary is not modified.GetValueOrDefault and the initial value of "test" is still in the dictionary.using System; using System.Collections.Generic; var items = new Dictionary<string, int>(); // Part 1: add the string with value 1. bool result = items.TryAdd("test", 1); Console.WriteLine("Added: " + result); // Part 2: the value already exists, so we cannot add it again. bool result2 = items.TryAdd("test", 2); Console.WriteLine("Added: " + result2); // Part 3: the value is still 1. Console.WriteLine(items.GetValueOrDefault("test"));Added: True Added: False 1
ContainsKeyThis commonly-called method sees if a given string is present in a Dictionary. We use string keys here—we look at more types of Dictionaries further on.
ContainsKey returns true if the key is found. Otherwise, it returns false. No exception is thrown if the key is missing.using System; using System.Collections.Generic; var dictionary = new Dictionary<string, int>(); dictionary.Add("apple", 1); dictionary.Add("windows", 5); // See whether Dictionary contains this string. if (dictionary.ContainsKey("apple")) { int value = dictionary["apple"]; Console.WriteLine(value); } // See whether it contains this string. if (!dictionary.ContainsKey("acorn")) { Console.WriteLine(false); }1 False
Int keysDictionary is a generic class. To use it, we must specify a type. This is a good feature—it means we can use an int key just as easily as a string key.
Dictionary with int keys. The values can also be any type.using System;
using System.Collections.Generic;
// Use int key type.
var data = new Dictionary<int, string>();
data.Add(100, "color");
data.Add(200, "fabric");
// Look up the int.
if (data.ContainsKey(200))
{
Console.WriteLine("CONTAINS KEY 200");
}CONTAINS KEY 200Extension methods can be used with Dictionary. We use the ToDictionary method—this is an extension method on IEnumerable. It places keys and values into a new Dictionary.
ToDictionary, from System.Linq, on a string array. It creates a lookup table for the strings.using System; using System.Linq; string[] values = new string[] { "One", "Two" }; // Create Dictionary with ToDictionary. // ... Specify a key creation method, and a value creation method. var result = values.ToDictionary(item => item, item => true); foreach (var pair in result) { Console.WriteLine("RESULT PAIR: {0}, {1}", pair.Key, pair.Value); }RESULT PAIR: One, True RESULT PAIR: Two, True
ContainsValueThis method tries to find a value in the Dictionary (not a key). It searches the entire collection, as no hashing is available on values.
Dictionary until it finds a match, or there are no more elements to check.ContainsValue is not as fast as ContainsKey. Using another Dictionary with keys of the values could help speed things up.using System;
using System.Collections.Generic;
var data = new Dictionary<string, int>();
data.Add("cat", 1);
data.Add("dog", 2);
// Use ContainsValue to see if the value is present with any key.
if (data.ContainsValue(1))
{
Console.WriteLine("VALUE 1 IS PRESENT");
}VALUE 1 IS PRESENTWe do not need to use Add to insert into a Dictionary. We can instead use the indexer with square brackets. This syntax also gets the value at the key.
using System;
using System.Collections.Generic;
var dictionary = new Dictionary<int, int>();
// We can assign with the indexer.
dictionary[1] = 2;
dictionary[2] = 1;
dictionary[1] = 3; // Reassign.
// Read with the indexer.
// ... An exception occurs if no element exists.
Console.WriteLine(dictionary[1]);
Console.WriteLine(dictionary[2]);3
1Dictionary provides a constructor that copies all values and keys into a new Dictionary instance. This constructor improves code reuse. It makes copying simpler.
Dictionary in this program is created, and has just 1 key and value pair in it.Dictionary to its constructor. We add a key. The copy has 2 keys—but "test" still has 1.using System; using System.Collections.Generic; var test = new Dictionary<int, int>(); test[20] = 30; // Copy the dictionary, and add another key, so we now have 2 keys. var copy = new Dictionary<int, int>(test); copy[30] = 40; Console.WriteLine("TEST COUNT: {0}", test.Count); Console.WriteLine("COPY COUNT: {0}", copy.Count);TEST COUNT: 1 COPY COUNT: 2
We can erase all pairs with the Clear method. Or we can assign the Dictionary variable to null. This causes little difference in memory usage—the entries are garbage-collected.
using System;
using System.Collections.Generic;
// Create a 1-key dictionary.
var d = new Dictionary<string, bool>();
d.Add("dog", true);
Console.WriteLine(d.Count);
// Clear the dictionary.
d.Clear();
Console.WriteLine(d.Count);1
0Dictionary fieldSometimes it is useful to have a Dictionary at the class level, as a field. And if we have a static class, we can initialize the Dictionary at the class level.
using System;
using System.Collections.Generic;
class Example
{
Dictionary<int, int> _lookup = new Dictionary<int, int>()
{
{1, 1},
{2, 3},
{3, 5},
{6, 10}
};
public int GetValue()
{
return _lookup[2]; // Example only.
}
}
class Program
{
static void Main()
{
// Create Example object instance.
var example = new Example();
// Look up a value from the Dictionary field.
Console.WriteLine("RESULT: " + example.GetValue());
}
}RESULT: 3KeyNotFoundExceptionThis error happens when we access a nonexistent key. With Dictionary we should test keys for existence first, with ContainsKey or TryGetValue.
using System.Collections.Generic;
class Program
{
static void Main()
{
var colors = new Dictionary<string, int>() { { "blue", 10 } };
// Use ContainsKey or TryGetValue instead.
int result = colors["fuchsia"];
}
}Unhandled Exception: System.Collections.Generic.KeyNotFoundException:
The given key was not present in the dictionary.
at System.Collections.Generic.Dictionary`2.get_Item(TKey key)
at Program.Main() in ...StringComparerWhen you do a lookup on a Dictionary of string keys, the string must be hashed. If we pass StringComparer.Ordinal to our Dictionary, we have a faster algorithm.
Dictionary with no comparer specified in its constructor.string "Las Vegas" in a Dictionary with StringComparer.Ordinal.string as "ordinal," we but gain performance on comparison and hashing (tested in 2021 on .NET 5).using System; using System.Collections.Generic; using System.Diagnostics; const int _max = 10000000; var data1 = new Dictionary<string, bool>(); data1["Las Vegas"] = true; var data2 = new Dictionary<string, bool>(StringComparer.Ordinal); data2["Las Vegas"] = true; // Version 1: do lookups on Dictionary with default string comparisons. Stopwatch s1 = Stopwatch.StartNew(); for (int i = 0; i < _max; i++) { if (!data1.ContainsKey("Las Vegas")) { return; } } s1.Stop(); // Version 2: do lookups in Dictionary that has StringComparer.Ordinal. Stopwatch s2 = Stopwatch.StartNew(); for (int i = 0; i < _max; i++) { if (!data2.ContainsKey("Las Vegas")) { return; } } s2.Stop(); Console.WriteLine(((double)(s1.Elapsed.TotalMilliseconds * 1000000) / _max).ToString("0.00 ns")); Console.WriteLine(((double)(s2.Elapsed.TotalMilliseconds * 1000000) / _max).ToString("0.00 ns"));16.40 ns ContainsKey, default comparer 15.17 ns ContainsKey, StringComparer.Ordinal
KeysHere we compare loops. A foreach-loop on KeyValuePairs is faster than the looping over Keys and accessing values in the loop body.
foreach-loop directly, without accessing the Keys property.Keys property, and then looks up each value in a foreach-loop.Dictionary and avoid lookups.using System; using System.Collections.Generic; using System.Diagnostics; const int _max = 1000000; var test = new Dictionary<string, int>(); test["bird"] = 10; test["frog"] = 20; test["cat"] = 60; int sum = 0; // Version 1: use foreach loop directly on Dictionary. var s1 = Stopwatch.StartNew(); for (int i = 0; i < _max; i++) { foreach (var pair in test) { sum += pair.Value; } } s1.Stop(); // Version 2: use foreach loop on Keys, then access values. var s2 = Stopwatch.StartNew(); for (int i = 0; i < _max; i++) { foreach (var key in test.Keys) { sum += test[key]; } } s2.Stop(); Console.WriteLine(s1.Elapsed.TotalMilliseconds); Console.WriteLine(s2.Elapsed.TotalMilliseconds);22.5618 ms Dictionary foreach 72.231 ms Keys foreach
The C# Dictionary, like all hash tables, is fascinating. It is fast—and useful in many places. Once we get a handle on its syntax, it is easy to use.