System.Threading.ThreadAbortException

.NET Exceptions – KeyNotFoundException

Next up in our continued .NET Exception Handling series we come to the System.Collections.Generic.KeyNotFoundException. In most cases the System.Collections.Generic.KeyNotFoundException is thrown when attempting to access a Collection element using a key that doesn’t exist within said collection.

In this article we’ll take a closer look at the System.Collections.Generic.KeyNotFoundException, including where it sits in the .NET exception hierarchy, along with some functional C# code examples to help illustrate how System.Collections.Generic.KeyNotFoundExceptions are typically thrown, as well as how to handle the differences in collection types (and the inability to natively capture the key that caused the exception in the first place). Let’s get to it!

The Technical Rundown

When Should You Use It?

Unfortunately, in spite of the fact that it belongs to the System.Collections namespace, the System.Collections.Generic.KeyNotFoundException sometimes has trouble capturing key errors from certain types of collections. For example, accessing an invalid key in List doesn’t throw a System.Collections.Generic.KeyNotFoundException, whereas doing the same in a Dictionary<TKey, TValue> does. To help illustrate some of these differences we’ll start with the full code sample below, after which we’ll go through the various methods and examples in more detail:

We start with a simple IBook interface and the Book class that implements it, which we’ll be using throughout our examples to create some data collections:

Next we define the ListExample(IReadOnlyList list) method, which will output the list before attempting to explicitly output the element at index list.Count (which, as we know, will exceed the existing indices by one since elements are zero-based):

We call the ListExample(IReadOnlyList list) method in our Program.Main() method by instantiating a new List collection to pass in:

As you can probably guess this throws an exception that we see in our log output, however, that thrown exception it isn’t a System.Collections.Generic.KeyNotFoundException:

As previously mentioned, unfortunately, using an invalid key within some collection types (such as List<>) produces a System.ArgumentOutOfRangeException. Let’s try a Dictionary<TKey, TValue> object instead and see what happens. Here we have the DictionaryExample(IReadOnlyDictionary<int, Book> dictionary) method that performs the exact same logic as ListExample(IReadOnlyList list), except with a passed in Dictionary<int, Book> instead:

To test this method we start by creating a new dictionary instance to pass to the method:

Executing this code now produces a System.Collections.Generic.KeyNotFoundException:

There’s one major problem though: The System.Collections.Generic.KeyNotFoundException class doesn’t provide any means of determining which key was the problematic key that wasn’t present and caused the exception in the first place. There are a few different ways to deal with this problem:

  • Use the Dictionary<TKey, TValue>.TryGetValue() method to explicitly check for a value at the specified key, then handle the boolean result of that method call to determine what should occur.
  • Create a custom ImprovedKeyNotFoundException class that inherits from System.Collections.Generic.KeyNotFoundException, and includes a .Key property to track the problematic key. Then create an extension method for Dictionary<TKey, TValue> that checks if a passed key exists in the dictionary. If the key isn’t found, throw a new ImprovedKeyNotFoundException with the associated problematic .Key property set for use in the exception output message.
  • Alternatively, create a custom ImprovedDictionary<TKey, TValue> class that inherits from Dictionary<TKey, TValue> and overrides the TValue method to perform a TryGetValue() method call for the provided key. If the key isn’t found, throw a new System.Collections.Generic.KeyNotFoundException with a modified Message that includes the problematic key value.

Implementing the first solution of using Dictionary<TKey, TValue>.TryGetValue() is quite easy, so we’ll start with that in the DictionaryUsingTryGetValueExample(IReadOnlyDictionary<int, Book> dictionary) method:

We’re passing the same Dictionary<TKey, TValue> instance that we used before to call this, so executing the above causes the TryGetValue() method to fail, outputting our custom failure message that includes the missing key:

The two remaining options for handling the invalid key are a bit more complex. However, of the two, the latter option of creating a new ImprovedDictionary<TKey, TValue> class that inherits from Dictionary<TKey, TValue> is far less code to implement, so we’ll go with that one. To do so we start with the new class:

Since this inherits Dictionary<TKey, TValue> the behavior is nearly identical to a normal dictionary, except for the extra check when retrieving a TValue. The downside, of course, is remembering to explicitly create dictionary instances using the new ImprovedDictionary<TKey, TValue> class. To test this out we’ve created just such an object here:

The result of our ImprovedDictionaryExample() method call attempting to access an invalid key element still throws a System.Collections.Generic.KeyNotFoundException, but notice that we successfully (and subtly) modified the exception Message property to include the problematic key:

To get the most out of your own applications and to fully manage any and all .NET Exceptions, check out the Airbrake .NET Bug Handler, offering real-time alerts and instantaneous insight into what went wrong with your .NET code, along with built-in support for a variety of popular development integrations including: JIRA, GitHub, Bitbucket, and much more.