I have some code that uses the GetOrAdd function from ConcurrentDictionary to add values with a unique key to a holder. However, I want to be able to tell if a values key is found (i.e. a true returned from TryGet) or whether it has to be added.
I haven't been able to find anything online that suggests this is achievable so if anyone has any ideas it would be really helpful.
This is my lock-free attempt. GetOrAdd can invoke a delegate if at the moment of its calling there was no such key but ultimately can drop return if another thread won. The idea is to create a new object inside the lambda and then compare it with what GetOrAdd returns, in case if it was started as "Add", to reduce comparison overhead. It is not yet proven to be reliable but for me looks atomic and performant.
I made it as an extension:
public static class ConcurrentDictionaryExtensions
{
public static bool TryGetOrAdd<TKey, TValue>(
this ConcurrentDictionary<TKey, TValue> dictionary,
TKey key,
out TValue resultingValue,
TValue existingValue = null) where TValue : class, new()
{
bool added = false;
TValue newValue = null;
resultingValue = dictionary.GetOrAdd(key, _ => {
newValue = existingValue is null ? new TValue() : existingValue;
added = true;
return newValue;
});
return added && ReferenceEquals(resultingValue, newValue);
}
}
Usage:
bool isNew = myDictionary.TryGetOrAdd(1, out var resultingValue, [existingValue]);
UPD: Added optional argument to pass existing object instead of creating new one. Extension seems to be proven to be reliable.
Here is an extension method GetOrAdd for the ConcurrentDictionary<TKey, TValue>, that has an additional out bool added parameter:
public static TValue GetOrAdd<TKey, TValue>(
this ConcurrentDictionary<TKey, TValue> source,
TKey key,
Func<TKey, TValue> valueFactory,
out bool added)
{
ArgumentNullException.ThrowIfNull(source);
ArgumentNullException.ThrowIfNull(valueFactory);
if (source.TryGetValue(key, out TValue value)) { added = false; return value; }
TValue newValue = valueFactory(key);
while (true)
{
if (source.TryAdd(key, newValue)) { added = true; return newValue; }
if (source.TryGetValue(key, out value)) { added = false; return value; }
}
}
This implementation alternates between the TryGetValue and the TryAdd, until one of them succeeds. It is probably less efficient than Niksr's TryGetOrAdd. It has the advantage of supporting value types for the TValue, not only reference types.
A proposal for including this functionality in the standard .NET libraries has been submitted here (Dec 23, 2022). As of this writing, the proposal is still open.
A similar implementation has been posted in a Microsoft blog post here by Stephen Toub. The implementation in this answer has the advantage that it never invokes the valueFactory more than once.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With