In this article, we are going to explore how to detect if a dictionary key exists in C#, when and why we should do that. Finally, we are going to have a final word on a common mistake when checking keys.
A dictionary is a data structure that maps keys to values, where each key is unique (there can be no duplicate keys) and to each key corresponds one and only one value.
We define a dictionary by specifying the type of the key and the type of the value, like Dictionary<string, int>
, meaning that it’s a dictionary where the keys are strings and their corresponding values are integers.
Let’s dive in.
Using ContainsKey in C#
We are going to start with a simple Dictionary:
Dictionary<string, int> MyDictionary = new Dictionary<string, int>() { { "Apple", 3 }, { "Banana", -2 }, { "Orange", 5 }, { "Pear", 2 } }; bool DictionaryHasBanana = MyDictionary.ContainsKey("Banana"); // true bool DictionaryHasKiwi = MyDictionary.ContainsKey("Kiwi"); // false bool DictionaryHasLowercaseBanana = MyDictionary.ContainsKey("banana"); // false
Calling ContainsKey()
on a dictionary returns true
if the given key is present or false
otherwise; this particular dictionary contains four unique keys and their corresponding values, so Kiwi
is a key and Banana
is a key (but not banana
, as strings are case sensitive).
When to Use ContainsKey?
Now let’s see what the ContainsKey
method does:
Dictionary<string, int> SetOnceDictionary = new Dictionary<string, int>() { { "Apple", 2 }, { "Banana", 3 } }; if (!SetOnceDictionary.ContainsKey("Apple")) { SetOnceDictionary["Apple"] = 4; } if (!SetOnceDictionary.ContainsKey("Kiwi")) { SetOnceDictionary["Kiwi"] = 4; }
It’s not strictly necessary to check if the dictionary already contains a key before assigning a value to it unless we want to update the value depending on whether the key exists or not. Since Apple
exists but Kiwi
does not, the resulting dictionary will keep its Apple
as-is and have one new key-value pair:
{ "Apple", 2 }, { "Banana", 3 }, { "Kiwi", 4 }
However, if we want to use a dictionary key we must check for its existence, lest we get a KeyNotFoundException
in case it doesn’t:
Dictionary<string, int> MyDictionary = new Dictionary<string, int>() { { "Apple", 3 }, { "Banana", -2 }, { "Orange", 5 }, { "Pear", 2 } }; // Works, but risky! int apples = MyDictionary["Apple"]; // 3 // Throws an exception! int kiwis = MyDictionary["Kiwi"]; int oranges = 0; if (MyDictionary.ContainsKey("Orange")) { oranges = MyDictionary["Orange"]; // 5 }
Even though it’s clear in the example that the dictionary contains the Apple
key, it’s good practice to always wrap our code with if (...ContainsKey(...))
if we want to use the dictionary on the right-hand side of the equality sign.
That’s because, in the generic expression lhs = rhs
, we are assigning the value of rhs
to an element named lhs
, and so rhs
must be well-defined for the expression to make sense.
The TryGetValue() Shortcut
The ContainsKey()
pattern is so ubiquitous that C# provides a shortcut to safely get the value mapped to a dictionary key if it exists:
Dictionary<string, int> MyDictionary = new Dictionary<string, int>() { { "Apple", 3 }, { "Banana", -2 }, { "Orange", 5 }, { "Pear", 2 } }; int apples = 0; bool ApplesSuccess = MyDictionary.TryGetValue("Apple", out apples); // ApplesSuccess == true, apples == 3 int kiwi = 0; bool KiwisSuccess = MyDictionary.TryGetValue("Kiwi", out kiwi); // KiwisSuccess == false, kiwi == 0
TryGetValue(key, out value)
has everything that ContainsKey(key)
has. Its first parameter represents the key we want to look for and it similarly returns true
or false
depending on whether the key exists or not.
However, it also has another feature: it has a second parameter with the out
keyword. When we pass an object to it, the function will modify it. In the example, apples
is 3
because that’s the value mapped to the Apple
key.
If we use TryGetValue()
on a key that does not exist, the out
value will be set to the default value for its type. In the kiwi
example, kiwi
is 0
because that’s the default value for its type, which is int
.
We should use the TryGetValue
method because it is faster than ContainsKey
.
Declare the Value in TryGetValue()
We can make the code even more succinct if we declare the out
variable right in the function call:
// Same process as before, but more succinct: bool ApplesSuccess = MyDictionary.TryGetValue("Apple", out int apples); bool KiwisSuccess = MyDictionary.TryGetValue("Kiwi", out var kiwi);
By using out int
(or out var
) we are simultaneously declaring a new variable and assigning a value to it.
When Does a Key Exist?
This question seems trivial, but it’s important when our dictionary key is not a primitive type, like int
or string
.
C# uses the equality comparer to determine if a key is present in a dictionary:
Dictionary<string, int> MyDictionary = new Dictionary<string, int>() { { "Apple", 3 }, { "Banana", -2 }, { "Orange", 5 }, { "Pear", 2 } }; bool DictionaryHasBanana = MyDictionary.ContainsKey("Banana"); // true
The dictionary contains Banana
because the string Banana
is equal to one key whose value is Banana
.
This is not as obvious if our key is a class type:
public class MyClass { public int MyNumber { get; set; } public MyClass(int num) { MyNumber = num; } } Dictionary<MyClass, int> MyDictionary = new Dictionary<MyClass, int>(); MyClass One = new MyClass(1); MyClass AnotherOne = new MyClass(1); MyDictionary.Add(One); bool DictionaryHas1 = MyDictionary.ContainsKey(AnotherOne); // false!
MyDictionary
does not contain AnotherOne
, even though One
and AnotherOne
appear to be one and the same. That’s because the two variables are references to two different objects in memory. When detecting if a key K
exists, for each key X
a dictionary checks:
- Is
X.GetHashCode() == K.GetHashCode()?
If so, - Is
X.Equals(K) == true?
If both answers are positive for any existing key X
, then K
is already part of the dictionary.
The standard implementation of GetHashCode()
returns a random integer; Equals()
returns true for a class type if the reference to the other object is the same (that is, they point to the same location in memory). This explains why there’s no key in MyDictionary
that satisfies both conditions for AnotherOne
.
GetHashCode()
and Equals()
are two functions that every class defines, and so we can override them to obtain our desired result:
public class MyClassWithEquality { public int MyNumber { get; set; } public MyClassWithEquality(int num) { MyNumber = num; } public bool Equals(MyClassWithEquality other) { if (this.MyNumber != other.MyNumber) return false; return true; } public override bool Equals(object obj) { if (obj is not MyClassWithEquality) return false; if (obj == null) return false; return Equals(obj as MyClassWithEquality); } public override int GetHashCode() { return MyNumber; } }
Now we can try our example again:
Dictionary<MyClassWithEquality, int> MyDictionary = new Dictionary<MyClassWithEquality, int>(); MyClassWithEquality One = new MyClassWithEquality(1); MyClassWithEquality AnotherOne = new MyClassWithEquality(1); MyDictionary.Add(One); bool DictionaryHas1 = MyDictionary.ContainsKey(AnotherOne); // true!
This time, ContainsKey
returns true because there’s at least one key X
so that X.GetHashCode() == AnotherOne.GetHashCode()
and X.Equals(AnotherOne)
, and that key is One
.
Conclusion
In this article, we’ve covered how to find a key in a dictionary and how to get values succinctly. Finally, we’ve detailed what it means for a key to exist in order to avoid one of the most common pitfalls when handling dictionary keys.