Dictionary
The 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
TryGetValue
This 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, -1
Keys
The 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 Remove
Count
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: 3
GetValueOrDefault
This 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 0
TryAdd
With 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
ContainsKey
This 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 200
Extension 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
ContainsValue
This 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 PRESENT
We 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 1
Dictionary
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 0
Dictionary
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: 3
KeyNotFoundException
This 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 ...
StringComparer
When 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
Keys
Here 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.