I know about Dictionary<K,V>.TryAdd()
method added in .NET Core 2.0 that improves performance by checking only once if the dictionary contains the key before adding the element unlike in:
if(!dico.ContainsKey(key)) { dico.Add(key,val); } // .Add() calls ContainsKey() a second time
However for performance reason I'd like to lazy build val
only if !dico.ContainsKey(key)
:
if(!dico.ContainsKey(key)) { dico.Add(key, new Value()); }
In this situation TryAdd()
degrades performance since the value is not lazy built.
dico.TryAdd(key,new Value());
Is there a way to both have a single ContainsKey()
call AND lazy build the value? Something like AddWithNoContainsKeyCheck()
:
if(!dico.ContainsKey(key)) { dico.AddWithNoContainsKeyCheck(key, new Value()); }
Yes, it is possible, by using the advanced API CollectionsMarshal.GetValueRefOrAddDefault
, available from .NET 6 and later:
/// <summary>
/// Uses the specified function to add a key/value pair to the dictionary,
/// if the key does not already exist.
/// </summary>
public static bool TryAdd<TKey, TValue>(
this Dictionary<TKey, TValue> dictionary,
TKey key,
Func<TKey, TValue> valueFactory,
out TValue value) where TKey : notnull
{
ArgumentNullException.ThrowIfNull(dictionary);
ArgumentNullException.ThrowIfNull(valueFactory);
ref TValue valueRef = ref CollectionsMarshal
.GetValueRefOrAddDefault(dictionary, key, out bool exists);
if (!exists)
{
try { valueRef = valueFactory(key); }
catch { dictionary.Remove(key); throw; }
value = valueRef;
return true;
}
value = valueRef; // It was `value = default` in the original answer
return false;
}
The CollectionsMarshal.GetValueRefOrAddDefault
returns a reference to the value stored in the dictionary. In case the valueFactory
fails, it is important to remove the newly added key. Otherwise a key with a default(TValue)
will be inadvertently added in the dictionary.
Note: The extension method TryAdd
in this answer offers the same functionality with the more convenient and more fluent GetOrAdd
. The bool
return value (conveying the information if the value was created or already existed) is rarely needed in practice.