17

There is a minor annoyance I find myself with a lot - I have a Dictionary<TKey, TValue> that contains values that may or may not be there.

So normal behaviour would be to use the indexer, like this:

object result = myDictionary["key"];  

However, if "key" is not in the dictionary this throws a KeyNotFoundException, so you do this instead:

object val;
if (!myDictionary.TryGetValue("key", out val))
{
    val = ifNotFound;
}

Which is fine, except that I can have a load of these in a row - TryGetValue starts to feel awful clunky.

So option 1 is an extension method:

public static TValue TryGet<TKey, TValue>(
    this Dictionary<TKey, TValue> input, 
    TKey key, 
    TValue ifNotFound = default(TValue))
{
    TValue val;
    if (input.TryGetValue(key, out val))
    {
        return val;
    }

    return ifNotFound;
}

Which lets me do:

object result = myDictionary.TryGet("key1") ?? ifNotFound;

int i = anotherDictionary.TryGet("key2", -1);

Which is simple enough, but an additional extension method with a name similar to existing instance methods potentially adds confusion and reduces maintainability. It's also not consistent with the dictionary's indexer set - that will handle missing keys.

So option 2 is a new implementation of IDictionary<TKey, TValue> with a implicit cast from Dictionary<TKey, TValue> but an indexer that returns default(TValue) instead of throwing a KeyNotFoundException.

That let's me do:

ForgivingDictionary<string, object> dict = myDictionary;

object val = dict["key"] ?? ifNotFound;

// do stuff to val, then...
dict["key"] = val;

So now the get and set values are consistent, but value types are messier and ForgivingDictionary involves a lot more code.

Both methods seem 'messy' - is there a better way to do this already in .Net?

Both methods make compromises that could cause confusion, but is one more obvious/clear than the other? And why?

Keith
  • 150,284
  • 78
  • 298
  • 434
  • 3
    Well, as far as I understand, you need just better name for your extension method, may be more specific - ex. SafeGetValue, GetOrUseValue, etc. Extension methods are quite common even in framework classes, so I guess you shouldn't be affraid of them. – Giedrius Jun 02 '11 at 13:21
  • 1
    IMO that extension method doesn't cause confusion and has the big advantage to be applicable to all dictionaries. – digEmAll Jun 02 '11 at 13:21
  • the `myDictionary.TryGet("key1") ?? ifNotFound;` syntax will only work for reference objects right? – Magnus Jun 02 '11 at 13:35
  • 4
    Of course, if your dictionary values are ints, doubles, decimals, etc., default(TValue) is going to return 0, and you'd have no way of discerning between a legitimate 0 that is associated with a key and a default 0 that comes back because the key doesn't exist. There is a place and a time to use the `bool ContainsKey(string key)` method. – Jay Jun 02 '11 at 13:39
  • @Magnus yeah, `myDictionary.TryGet("key2", valueIfNotFound)` will work for value types, or you can do `myDictionary.TryGet("key2")` and get the default for the value type. – Keith Jun 02 '11 at 13:40
  • 2
    IMHO, the 'Try' prefix should be used exclusively with methods that take in at least one out parameter and returns a bool, to avoid confusion. – blizpasta Jun 02 '11 at 13:45
  • @Jay - yes, I'd still expect to use `TryGetValue` sometimes, for instance if the way to get a value if the dictionary value is missing is expensive. – Keith Jun 02 '11 at 13:46
  • related http://stackoverflow.com/questions/254178/c-sharp-dictionaries-valueornull-valueordefault – nawfal May 05 '13 at 01:03
  • @nawfal yeah, though that's a simpler problem - I already know that `TryGetValue` is better than `x.ContainsKey(key) ? x[key] : something` and I was wondering about the best way of simplifying the call to `TryGetValue`. The answer to their question is kind of the starting point for mine. – Keith May 07 '13 at 11:06

3 Answers3

9

When naming an extension method intended to replace an existing method, I tend to add to the method name for specificity rather than shortening it:

GetValueOrDefault(...)

As for the ForgivingDictionary, you can constrain TKey so that it can't be a value type. However, if you must deal with value types in it, you're going to return something for a value type and the best option is to return default(TKey) since you can't return null.

Honestly, I'd go with the extension method.

Edit: GetValueOrDefault(), of course, wouldn't add to the dictionary if it didn't find the key. I would just return a default value if it wasn't found, because that's how it's named. If one wanted it to insert as well, a good name would be GetValueOrInsertDefault().

Joel B Fant
  • 24,406
  • 4
  • 66
  • 67
  • 1
    I would do the extension method as well, and use the same name. With intellisense and autocomplete, having a slightly longer name like that is hardly noticeable. :) A method's name should be clear and help identify what it does without needing to look it up. – Joshua Jun 02 '11 at 14:39
  • Fair enough - it seems the consensus is that the extension method's best but use a more descriptive name. – Keith Jun 02 '11 at 14:43
  • The extension method doesn't add to the dictionary though, it just wraps `TryGetValue` – Keith Jun 02 '11 at 14:48
  • @Keith: Yeah, that didn't come out right. I rephrased it. I had added that bit on from recently doing something similar myself, to make populating dictionaries a bit easier (specifically, `Dictionary>`-style population). – Joel B Fant Jun 02 '11 at 14:59
1

I'm unable to infer from your question what should be done when a key is not found. I can imagine nothing should be done in that case, but I can also imagine the opposite. Anyway, an elegant alternative for a series of these TryGetValue-statements you describe, is using one of the following extension methods. I have provided two options, depending on whether something should be done or not when the dictionary does not contain the key:

/// <summary> Iterates over all values corresponding to the specified keys, 
///for which the key is found in the dictionary. </summary>
public static IEnumerable<TValue> TryGetValues<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, IEnumerable<TKey> keys)
{
    TValue value;
    foreach (TKey key in keys)
        if (dictionary.TryGetValue(key, out value))
            yield return value;
}

/// <summary> Iterates over all values corresponding to the specified keys, 
///for which the key is found in the dictionary. A function can be specified to handle not finding a key. </summary>
public static IEnumerable<TValue> TryGetValues<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, IEnumerable<TKey> keys, Action<TKey> notFoundHandler)
{
    TValue value;
    foreach (TKey key in keys)
        if (dictionary.TryGetValue(key, out value))
            yield return value;
        else
            notFoundHandler(key);                        
}

Example code on how to use this is:

TKey[] keys = new TKey{...};
foreach(TValue value in dictionary.TryGetValues(keys))
{
    //some action on all values here
}
JBSnorro
  • 6,048
  • 3
  • 41
  • 62
  • 1
    When a key is not found it returns `default(TValue)`, which will be `null` for a reference type and the default for a value type (`0` for an `int` and so on). I'm not sure your `TryGetValues` method does what I need - I think in that circumstance I might use a Linq `join` instead. – Keith Jun 02 '11 at 14:20
0

Or perhaps

public static TValue TryGet<TKey, TValue>(this Dictionary<TKey, TValue> input, 
                                                   TKey key)
{

     return input.ContainsKey(key) ? input[key] : *some default value*;

}
andreea
  • 86
  • 1
  • 8
  • 1
    That requires two explicit lookups, whereas the OP's method only requires one, so IMHO is not as optimal. – Mike Chamberlain Jun 03 '11 at 12:57
  • @Mikey Cee has it right `dictionary.TryGetValue(key, out val)` is quicker than `dictionary.ContainsKey(key) ? input[key] : default`. My method also used [`default(TValue)`](http://msdn.microsoft.com/en-us/library/xwth0h0d(v=vs.80).aspx) or an optional value to use if the key isn't found. – Keith Jun 03 '11 at 13:57