Click here to Skip to main content
15,867,568 members
Articles / Programming Languages / C#
Article

A DiagnosticDictionary

Rate me:
Please Sign up or sign in to vote.
4.11/5 (12 votes)
19 Nov 2008CPOL2 min read 37.7K   93   17   9
Making the "the given key was not present" message more informative.

Introduction

I hope I am not the only person to have ever experienced the dreaded KeyNotFoundException message:

Unhandled Exception: System.Collections.Generic.KeyNotFoundException: The given key was not present in the dictionary.

This leaves everyone wondering, what was the key? What was the dictionary?

Certainly, the programmer could wrap every line of code that uses a dictionary indexer with a try-catch block, or at least some outer method, but even then, the exception does not tell you the key that caused the exception. So, this is a very short article presenting a simple implementation which overrides the Dictionary<> class. I also look at whether extension methods are suitable to achieve the desired behavior.

The DiagnosticDictionary Class

To help with this problem, I've created a DiagnosticDictionary that overrides (with the loathsome "new" keyword) the indexer of the generic dictionary. It catches KeyNotFoundException, and re-throws it with an attempt to describe the key and the dictionary name, which can be supplied in the constructor. As a bonus, I've also added a Tag property that can be used to associate any object with the dictionary.

Implementation

Copy and paste the following code, and replace your Dictionary constructor with DiagnosticDictionary.

C#
using System;
using System.Collections.Generic;

namespace Clifton.Collections.Generic
{
  /// <summary>
  /// A dictionary with an indexer that produces an informative
  /// KeyNotFoundException message.
  /// </summary>
  public class DiagnosticDictionary<TKey, TValue> : Dictionary<TKey, TValue>
  {
    protected object tag;
    protected string name = "unknown";

    /// <summary>
    /// Gets/sets an object that you can associate with the dictionary.
    /// </summary>
    public object Tag
    {
      get { return tag; }
      set { tag = value; }
    }

    /// <summary>
    /// The dictionary name. The default is "unknown". 
    /// Used to enhance the KeyNotFoundException.
    /// </summary>
    public string Name
    {
      get { return name; }
      set { name = value; }
    }

    /// <summary>
    /// Parameterless constructor.
    /// </summary>
    public DiagnosticDictionary()
    {
    }

    /// <summary>
    /// Constructor that takes a name.
    /// </summary>
    public DiagnosticDictionary(string name)
    {
      this.name = name;
    }

    /// <summary>
    /// Indexer that produces a more useful KeyNotFoundException.
    /// </summary>
    public new TValue this[TKey key]
    {
      get
      {
        try
        {
          return base[key];
        }
        catch (KeyNotFoundException)
        {
          throw new KeyNotFoundException("The key '" + key.ToString() + 
             "' was not found in the dictionary '"+name+"'");
        }
      }

      set { base[key] = value;}
    }
  }
}

Given the test code:

C#
DiagnosticDictionary<string, string> d2 = 
       new DiagnosticDictionary<string, string>("Test");
string b = d2["b"];

the DiagnosticDictionary will throw a more useful exception:

Unhandled Exception: System.Collections.Generic.KeyNotFoundException: The key 
   'b' was not found in the dictionary 'Test'

Note that it now tells you the key and the dictionary name.

Alternate Implementation: Extension Methods

The following illustrates using an Extension Method (courtesy of CPian Wouter Ballet, see the article comments below):

C#
public static class DiagnosticDictionary
{
  public static TValue DiagItem<TKey, TValue>(this IDictionary<TKey, 
                                              TValue> d, TKey key)
  {
    try
    {
      return d[key];
    }
    catch (KeyNotFoundException)
    {
      throw new KeyNotFoundException("The key '" + key + 
          "' was not found in the dictionary.");
    }
  }
}

You will, though, have to change how you index the key:

C#
Dictionary<string, string> d2 = new Dictionary<string, string>();
string b = d2.DiagItem("b");

So, I think it's more an architectural choice that one needs to make early in the project. In my opinion, if you are starting a project, then using an Extension Method makes more sense than refactoring all the indexers in an existing project.

Why Wouldn't You Use DiagnosticDictionary?

First is the performance hit of going through the DiagnosticDictionary indexer, which then calls the base class indexer.

Second is security. Let's say, you have a dictionary defined like (dubious at best, but it's an example):

C#
Dictionary<Hash password, Rights rights>

You certainly wouldn't want the exception to emit the password key!

What About That ToString() ?

The call to key.ToString() is fine for value types and strings. If you have a more complex structure or class for the key, then you might consider overriding the ToString() method to provide a more informative description of the contents of the struct/class.

Conclusion

Hopefully, people will find this implementation as useful as my client's QA folks, the DB admin guru, devs, and even myself. :)

Special thanks to CPian Wouter Ballet for showing me how to use Generics in an Extension Method.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect Interacx
United States United States
Blog: https://marcclifton.wordpress.com/
Home Page: http://www.marcclifton.com
Research: http://www.higherorderprogramming.com/
GitHub: https://github.com/cliftonm

All my life I have been passionate about architecture / software design, as this is the cornerstone to a maintainable and extensible application. As such, I have enjoyed exploring some crazy ideas and discovering that they are not so crazy after all. I also love writing about my ideas and seeing the community response. As a consultant, I've enjoyed working in a wide range of industries such as aerospace, boatyard management, remote sensing, emergency services / data management, and casino operations. I've done a variety of pro-bono work non-profit organizations related to nature conservancy, drug recovery and women's health.

Comments and Discussions

 
GeneralMy vote of 1 Pin
Joe Sonderegger25-Nov-08 3:29
Joe Sonderegger25-Nov-08 3:29 
GeneralRe: My vote of 1 Pin
Marc Clifton26-Nov-08 2:45
mvaMarc Clifton26-Nov-08 2:45 
GeneralThoughts Pin
PIEBALDconsult20-Nov-08 3:14
mvePIEBALDconsult20-Nov-08 3:14 
GeneralRe: Thoughts Pin
supercat920-Nov-08 8:58
supercat920-Nov-08 8:58 
GeneralSome suggestions... Pin
Wouter Ballet19-Nov-08 21:50
Wouter Ballet19-Nov-08 21:50 
Hi Marc,

Just thought I'd let you know that extension methods do allow generics to be used alright (LINQ depends on it). The only thing you have to provide are the generic arguments. You won't have to type these because they will be inferred by the compiler.
Another suggestion is to make it apply to IDictionary<> instead of Dictionary<>.

Consider the following example:

public static class DiagnosticDictionary
{
    public static TValue DiagItem<TKey, TValue>(this IDictionary<TKey, TValue> d, TKey key)
    {
        try
        {
            return d[key];
        }
        catch (KeyNotFoundException)
        {
            throw new KeyNotFoundException("The key '" + key +
                "' was not found in the dictionary.");
        }
    }
}

Kind Regards,
Wouter
GeneralRe: Some suggestions... Pin
Marc Clifton20-Nov-08 1:16
mvaMarc Clifton20-Nov-08 1:16 
GeneralRe: Some suggestions... Pin
Wouter Ballet20-Nov-08 1:32
Wouter Ballet20-Nov-08 1:32 
GeneralRe: Some suggestions... Pin
Marc Clifton20-Nov-08 2:07
mvaMarc Clifton20-Nov-08 2:07 
GeneralRe: Some suggestions... Pin
PIEBALDconsult20-Nov-08 3:00
mvePIEBALDconsult20-Nov-08 3:00 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.