Building a custom GetOrAdd method for ConcurrentDictionary<TKey,TValue>

I was recently asked by a developer about getting some additional information out of ConcurrentDictionary<TKey,TValue>’s GetOrAdd method. 

As a reminder, GetOrAdd either returns the value for a key currently in the dictionary, or if that key doesn’t have a value, it adds a value for the key as dictated by either a TValue provided by the caller or by executing a Func<TKey,TValue> provided by the caller.  It then returns the new value.  However, it doesn’t tell the caller which happened; all the caller knows is that it’s handed back the value (existing or new) that was associated with the key.  The developer wanted to know which occurred, as he needed to notify some other code that a new value had been added.

While the built-in GetOrAdd method doesn’t provide this, we can build our own GetOrAdd overload to provide this behavior, building it on top of the existing TryGetValue and TryAdd methods:

public static TValue GetOrAdd<TKey, TValue>(
    this ConcurrentDictionary<TKey, TValue> dict,
   
TKey key, Func<TKey, TValue> generator,
    out bool added)
{
    TValue value;
    while (true)
    {
        if (dict.TryGetValue(key, out value)) 
        { 
            added = false
            return value; 
        }

        value = generator(key);
        if (dict.TryAdd(key, value)) 
        { 
            added = true
            return value; 
        }
    }
}

The approach here is straightforward.  We try to get the key’s value in the dictionary.  If we can, then we use an out parameter to indicate that a new value was not added and we return the value we retrieved.  If we can’t, then we try to add a new value as generated by the provided Func<TKey,TValue>; assuming that succeeds, we note the addition via the out parameter and we return the newly added value.  If the addition fails (which should only happen if another thread concurrently added an item since we called TryGetValue), then we loop around and try the whole process again.

Using the TryGetValue, TryAdd, and TryUpdate on ConcurrentDictionary<TKey,TValue>, it’s possible to build many variations of this kind of behavior.