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.

Published Tue, Feb 5 2008 23:32 by skeet
Filed under:

Comments

# re: A simple extension method, but a beautiful one

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;

Tuesday, February 05, 2008 6:53 PM by Steve Johnson

# re: A simple extension method, but a beautiful one

Yeah; that's a great utility.

I posted the one we use at work to code.logos.com/.../getoraddvalue.html

Let me know what you think.

Tuesday, February 05, 2008 7:27 PM by Jacob

# re: A simple extension method, but a beautiful one

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.

Tuesday, February 05, 2008 8:02 PM by calinoiu

# re: A simple extension method, but a beautiful one

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

public static TValue GetOrCreate<TKey, TValue>(this IDictionary<TKey, TValue> 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<TKey, TValue>(this IDictionary<TKey, TValue> 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<TValue>();

public static TValue GetOrCreate<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key, Generator<TValue> 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.

Wednesday, February 06, 2008 12:36 AM by Tommy Carlier

# re: A simple extension method, but a beautiful one

The reason for not using TryGetValue is a simple but embarrassing one - I looked for it on MSDN when looking at what IDictionary<TKey,TValue> had available (as opposed to Dictionary<TKey,TValue>) 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 :)

Wednesday, February 06, 2008 1:41 AM by skeet

# re: A simple extension method, but a beautiful one

One problem with the TryGetXXX pattern it's lack of proximity to GetXXX in intellisense and help files.

Wednesday, February 06, 2008 9:30 AM by PeterRitchie

# re: A simple extension method, but a beautiful one

Since long we have created variant of it

public static T GetOrSet<T>(string key, int minutes, Cache.Getter<T> getter)

{

 T item = GlobalCache.Get<T>(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

Friday, February 08, 2008 12:01 PM by Ryan Heath

# re: A simple extension method, but a beautiful one

Isn't it simpler?

<code>

TValue result;

if (dictionary.TryGetValue(key, out result))

{

   return result;

}

return dictionary[key] = new TValue();

</code>

This way you may init or generate value too.

And shortest code:

<code>

TValue result;

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

</code>

:)

Friday, June 06, 2008 5:17 AM by Quiz

# re: A simple extension method, but a beautiful one

@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

Friday, June 06, 2008 6:50 AM by skeet