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!
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:
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.
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;
LikeLike
Yeah; that’s a great utility.
I posted the one we use at work to http://code.logos.com/blog/2008/02/getoraddvalue.html
Let me know what you think.
LikeLike
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.
LikeLike
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.
LikeLike
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 :)
LikeLike
One problem with the TryGetXXX pattern it’s lack of proximity to GetXXX in intellisense and help files.
LikeLike
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
LikeLike
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();
:)
LikeLike
@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
LikeLike