Monday, April 04, 2011

Little-known gems: Atomic conditional removals from ConcurrentDictionary

ConcurrentDictionary<TKey,TValue>, first introduced in .NET 4, is an efficient dictionary data structure that enables thread-safe reading and writing, meaning that multiple threads may all be accessing the dictionary at the same time without corrupting it.  It supports adding through its TryAdd method, conditional updates through its TryUpdate method, non-conditional adds or updates through its indexer's setter, and removals through its TryRemove method, through which a key/value pair is removed if the user-provided key matches.  It also has several compound methods, such as GetOrAdd and AddOrUpdate.  Lately, however, we've seen several folks ask for further support on ConcurrentDictionary, that of removing a key/value pair only if both the user-provided key and value match the corresponding pair currently stored in the dictionary.

You want it, you got it!  I mean, literally, you already have it.  As with Dictionary<TKey,TValue>, ConcurrentDictionary<TKey,TValue> implements
ICollection<KeyValuePair<TKey,TValue>>. ICollection<T> exposes a Remove<T> method, so ICollection<KeyValuePair<TKey,TValue>> has a Remove(KeyValuePair<TKey,TValue>) method.  Dictionary<TKey,TValue> implements Remove such that it only removes the element if both the key and the value match the data in the dictionary, and thus ConcurrentDictionary<TKey,TValue> does the same.  And as it's a concurrent data structure, ConcurrentDictionary<TKey,TValue> ensures that the comparison is done atomically with the removal.  Tada!

Of course, just as with Dictionary<TKey,TValue>, ConcurrentDictionary<TKey,TValue> implements ICollection<KeyValuePair<TKey,TValue>> explicitly, so if you want access to this method, you'll need to first cast the dictionary to the interface.  Here's an extension method to help you with that cause:

public static bool TryRemove<TKey, TValue>(  this ConcurrentDictionary<TKey, TValue> dictionary, TKey key, TValue value)
{
    if (dictionary == null) throw new ArgumentNullException("dictionary");
    return ((ICollection<KeyValuePair<TKey, TValue>>)dictionary).Remove(
        new KeyValuePair<TKey, TValue>(key, value));
}