Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Which mechanism is a better way to extend Dictionary to deal with missing keys and why?

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?

like image 787
Keith Avatar asked Jun 02 '11 13:06

Keith


People also ask

How do you extend a dictionary?

update() to extend a dictionary. Use dict. update(other) to extend a dictionary dict with the items from another dictionary other .

How to add key without value in dictionary Python?

You can add a new key to the dictionary without value using the None keyword. It'll just add a new key to the dictionary.

What happens when you try to add a duplicate key in a dictionary?

As other have said, it is not possible to add duplicate key in dictionary. How about using Dictionary<string, List<string>>? Then check if the key exist then add the line in the list value for that key and if the key does not exist then create a new entry in the dictionary.


2 Answers

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().

like image 121
Joel B Fant Avatar answered Oct 05 '22 23:10

Joel B Fant


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
}
like image 24
JBSnorro Avatar answered Oct 06 '22 00:10

JBSnorro