A simple extension method, but a beautiful one

This came up a little while in a newsgroup question, and Marc Gravell and I worked out a solution between us. I’ve finally included it in MiscUtil (although not released it yet – there’s a lot of stuff ready to go when we’ve finalised namespaces and updated the website etc) but I thought I’d share it here.

How often have you written code to do something like counting word frequencies, or grouping items into lists? I know a lot of this can be solved with LINQ if you’re using .NET 3.5, but in .NET 2.0 we’ve always been nearly there. Dictionaries have provided a lot of the necessary facilities, but there’s always the bit of code which needs to check whether or not we’ve already seen the key, and populate the dictionary with a suitable initial value if not – a count of 0, or an empty list for example.

There’s something that 0 and “empty list” have in common. They’re both the results of calling new TValue() for their respect TValue types of int and List<Whatever>. Can you see what’s coming? A generic extension method for dictionaries whose values are of a type which can use a parameterless constructor, which returns the value associated with a key if there is one, or a new value (which is also inserted into the dictionary) otherwise. It’s really simple, but it’ll avoid duplication all over the place:

Note: This code has been updated due to comments below. Comments saying “Use TryGetValue” referred to the old version!


 

public static TValue GetOrCreate<TKey, TValue>(this IDictionary<TKey, TValue> dictionary,
                                               TKey key)
    where TValue : new()
{
    TValue ret;
    if (!dictionary.TryGetValue(key, out ret))
    {
        ret = new TValue();
        dictionary[key] = ret;
    }
    return ret;
}

The usage of it might look something like this:

 

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

foreach (string word in someText)
{
    dict[word] = dict.GetOrCreate(word)+1;
}

I’m not going to claim this will set the world on fire, but I know I’m fed up with writing the kind of code which is in GetOrCreate, and maybe you are too.


Additional overloads are available to specify either a value to use when the key is missing, or a delegate to invoke to create a value.

9 thoughts on “A simple extension method, but a beautiful one”

  1. If you use TryGetValue, you can avoid the 2nd lookup. No biggie, but probably worth the one-time effort.

    TValue ret = null;
    if(!dictionary.TryGetValue(key, out ret))
    {
    ret = new TValue();
    dictionary.Add(key, ret);
    }

    return ret;

  2. Any particular reason not to use TryGetValue in place of the ContainsKey + indexer getter approach? The TryGetValue approach ought to offer an overall performance gain, particularly given that most implementation classes aren’t likely to be caching the entry found in the ContainsKey call.

  3. Calinoiu beat me to it. TryGetValue is faster because it only has to search once. ContainsKey + indexer = 2 searches.

    public static TValue GetOrCreate(this IDictionary dictionary, TKey key)
    where TValue : new()
    {
    TValue ret;
    if ( ! dictionary.TryGetValue(key, out ret))
    {
    dictionary[key] = ret = new TValue();
    }
    return ret;
    }

    You could also have an overload that accepts an initial value:

    public static TValue GetOrCreate(this IDictionary dictionary, TKey key, TValue initialValue)
    {
    TValue ret;
    if ( ! dictionary.TryGetValue(key, out ret))
    {
    dictionary[key] = ret = initialValue;
    }
    return ret;
    }

    Or an overload that accepts a function to generate the value:

    public delegate TValue Generator();

    public static TValue GetOrCreate(this IDictionary dictionary, TKey key, Generator generator)
    {
    TValue ret;
    if ( ! dictionary.TryGetValue(key, out ret))
    {
    dictionary[key] = ret = generator();
    }
    return ret;
    }

    In both overloads, the “where TValue : new()” is not even necessary.

  4. The reason for not using TryGetValue is a simple but embarrassing one – I looked for it on MSDN when looking at what IDictionary had available (as opposed to Dictionary) but failed to see it somehow. Will change the MiscUtil code accordingly.

    And yes, I agree about the overloads – particularly the one taking a delegate. In fact, the delegate version was the one I originally considered – the use of a constructor constraint was an improvement for a common case :)

  5. Since long we have created variant of it

    public static T GetOrSet(string key, int minutes, Cache.Getter getter)
    {
    T item = GlobalCache.Get
    (key);

    if (item == null)
    {
    // get item from delegate
    item = getter();
    if (item == null)
    return default(T);

    GlobalCache.Set(key, item, minutes);
    }

    return item;
    }

    And we use it like this, for example:

    return GlobalCache.GetOrSet(key, 5, () => SP.UserGetLastLoggedInList(startRow, endRow));

    Which caches for 5 minutes or if not present calls value from the DB

    // Ryan

  6. Isn’t it simpler?

    TValue result;
    if (dictionary.TryGetValue(key, out result))
    {
    return result;
    }
    return dictionary[key] = new TValue();

    This way you may init or generate value too.
    And shortest code:

    TValue result;
    return dictionary.TryGetValue(key, out result) ? result : dictionary[key] = new TValue();

    :)

  7. @Quiz: I don’t offhand know if that would compile – I don’t know whether the result of the assignment expression is the original value (as it would be in the case of a normal variable assignment, of course). Given the “I don’t know” element, I personally find my version simpler :)

    Jon

Comments are closed.