views:

178

answers:

4

When you try to access a key which isn't in a Dictionary (for example), here's the stack trace you get :

   at System.ThrowHelper.ThrowKeyNotFoundException()
     at System.Collections.Generic.Dictionary`2.get_Item(TKey key)   
       .... .... (my own code stack trace)

Like most people probably do, I log this errors when they occur, and try to figure out what happened.

The two key informations I want are where did this occur (the stacktrace is very helpful for that), and the key which caused the exception, which doesn't appear anywhere.

Either I didn't look properly (the KeyNotFoundException class contains a "Data" member, which is always empty, and the "Message" field" doesn't contain the key value), or it wasn't included in the framework at all.

I can't imagine nobody in the .net BCL team thought this would be an useful feature to have. I'm curious to know why they didn't include it. Are there some good reasons not to?

How do you deal with those exceptions ? The only alternative I can think of is to use a custom extension method on Dictionary which would wrap the call, catch the exception, and rethrow it with additional information regarding the key, but that wouldn't help for code I don't own, and it feel broken to change such a "base" functionality.

What do you think ?


I'm aware of this question, regarding the general fact that one cannot access the arguments of the method which raised an exception. My question is specifically related to the KeyNotFoundException.


Edit : I'm aware of the TryGetValue pattern, I do that all the time when I expect that my collection may not contain the key. But when it should contain it, I don't test, because I prefer my program to fail with an exception so that I know something unexpected happened before (ie at the time when the key should have been inserted)

I could wrap a try/catch/log error/rethrow around all my dictionary access, but this would lead to difficult-to-read code, cluterred with all my exception handling/logging stuff.

+1  A: 

You could use the ContainsKey or TryGetValue method instead to verify if the dictionary contains the key.

Darin Dimitrov
+1  A: 

As darin pointed out, you should really be account for this in your code, but if for whatever reason, that's not possible, you could do the following.

public object GetObjectFromDictionary(string key)
{
    try
    {
        return MyDictionary[key];
    }
    catch (KeyNotFoundException kex)
    {
        throw new WrappedException("Failed To Find Key: " + key, kex);
    }
}
Eoin Campbell
Ungeneric code - How ugly ;-)
Dario
+3  A: 

Why do you rely on (slow) exceptions? Just verify whether the key exists with ContainsKey or TryGetValue?

I don't know the reason why the exception doesn't contain the error-causing field (maybe because it should be ungeneric) but just wrap it if you think you'll need it.

class ParameterizedKeyNotFoundException<T> : KeyNotFoundException {
    public T InvalidKey { get; private set; }

    public ParameterizedKeyNotFoundException(T InvalidKey) {
        this.InvalidKey = InvalidKey;
    }
}

static class Program {

    static TValue Get<TKey, TValue>(this IDictionary<TKey, TValue> Dict, TKey Key) {
        TValue res;

        if (Dict.TryGetValue(Key, out res))
            return res;

        throw new ParameterizedKeyNotFoundException<TKey>(Key);
    }

    static void Main(string[] args) {

        var x = new Dictionary<string, int>();

        x.Add("foo", 42);

        try {
            Console.WriteLine(x.Get("foo"));
            Console.WriteLine(x.Get("bar"));
        }
        catch (ParameterizedKeyNotFoundException<string> e) {
            Console.WriteLine("Invalid key: {0}", e.InvalidKey);
        }

        Console.ReadKey();
    }
}
Dario
regarding the use of TryGetValue : http://stackoverflow.com/questions/887097/should-i-always-use-trygetvalue-to-access-net-dictionaries
Brann
+1 for good explanation and using 42 as an example value :D
Perica Zivkovic
A: 

I can't really enlight you on why the didn't include the key in the exception. As darin stated, you should be always using TryGetValue - it makes the code easier to read, to maintain and you won't get a KeyNotFoundException (and, while your at it, always use TryParse instead of Parse, for int, double, DateTime, whatever, for the same reason).

However, once you got them:

How do you deal with those exceptions ?

Since you know where the exception is thrown, if the code is not utterly complex, this allows you to reproduce the bug (enable "Break on thrown exception", or whatever it's called in the English version, in the Debugging->Exceptions dialog for this exception to break in that very moment), and I usually find out what the reason is in a few minutes. The debugger tells you what the key is in that case, and then it's your work to find out why that's the key (or why the key isn't in the dictionary) anyway, even if you got the key name through a bug report. Unless you have some kind of god dictionary where absolutely everything is in, it should be rather easy to figure out where the problem is.

There shouldn't be any unhandled exceptions left in the code, so it should be a rare case to encounter such a thing, and then it's ok for me to investigate a little further. In other words, I never felt the need to get the key from a bug report.

Of course, well, if you can't reproduce the bug, this won't help you - I then typically add logging code to the failing area and hand that version out to the customer that encountered the bug (if it's more than one customer, it's typically possible to reproduce the bug with the customer's data).

But then, I'm developing software for other companies and not shrink-wrap software, so things might be different for you regarding the time you can spend with the customer.

OregonGhost