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.

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
Become a patron at Patreon!
To download the source code for this article, you can visit our GitHub repository.

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:

  1. Is X.GetHashCode() == K.GetHashCode()? If so,
  2. 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.

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!