.NET Concurrent Dictionary Exchange Value

106 views Asked by At

Is there a data structure similar to ConcurrentDictionary<TKey,TValue> and Interlocked.Exchange<T>(...) that would allow you atomically to set a new value and retrieve the old value assigned to an arbitrary key? Doesn't seem possible with any of the AddOrUpdate(...) overloads.

2

There are 2 answers

7
Theodor Zoulias On BEST ANSWER

This functionality is not included out of the box. An API proposal for a Transact method exists in the GitHub dotnet/runtime repository, that would offer this functionality, but currently hasn't created much traction. You could implement it manually though, using the rudimentary TryGetValue, TryAdd and TryUpdate methods:

public static TValue AddOrUpdate<TKey, TValue>(
    this ConcurrentDictionary<TKey, TValue> source,
    TKey key,
    Func<TKey, TValue> addValueFactory,
    Func<TKey, TValue, TValue> updateValueFactory,
    out bool oldValueExists,
    out TValue oldValue) where TKey : notnull
{
    ArgumentNullException.ThrowIfNull(source);
    ArgumentNullException.ThrowIfNull(addValueFactory);
    ArgumentNullException.ThrowIfNull(updateValueFactory);
    while (true)
    {
        if (source.TryGetValue(key, out oldValue))
        {
            TValue newValue = updateValueFactory(key, oldValue);
            if (source.TryUpdate(key, newValue, oldValue))
            {
                oldValueExists = true;
                return newValue;
            }
        }
        else
        {
            TValue newValue = addValueFactory(key);
            if (source.TryAdd(key, addValueFactory(key)))
            {
                oldValueExists = false;
                oldValue = default;
                return newValue;
            }
        }
    }
}

The above implementation is a modified version of the existing AddOrUpdate implementation.

2
Charlieface On

You could use the following extension functon

public static TValue Exchange<TKey, TValue>(this ConcurrentDictionary<TKey, TValue> source, TKey key, TValue value)
    where TKey : notnull
{
    TValue oldValue = default;
    _ = dict.AddOrUpdate(key,
        key => { oldValue = default; return newValue; },
        (key, old) => { oldValue = old; return newValue; });
    return oldValue;
}