Starting from .NET 6, it is now possible to implement a GetOrAdd
extension method for the Dictionary<TKey, TValue>
class that takes a key
and a valueFactory
, and hashes the key only once. The new API is the CollectionsMarshal.GetValueRefOrAddDefault
method, with this signature:
// Gets a reference to a TValue in the specified dictionary, adding a new entry
// with a default value if the key does not exist.
public static ref TValue? GetValueRefOrAddDefault<TKey,TValue> (
Dictionary<TKey,TValue> dictionary, TKey key, out bool exists);
This is a ref
returning method. It can be used to implement the GetOrAdd
like this:
/// <summary>
/// Adds a key/value pair to the dictionary by using the specified function
/// if the key does not already exist. Returns the new value, or the
/// existing value if the key exists.
/// </summary>
public static TValue GetOrAdd<TKey, TValue>(
this Dictionary<TKey, TValue> dictionary,
TKey key,
Func<TKey, TValue> valueFactory)
{
ArgumentNullException.ThrowIfNull(dictionary);
ArgumentNullException.ThrowIfNull(valueFactory);
ref TValue value = ref CollectionsMarshal
.GetValueRefOrAddDefault(dictionary, key, out bool exists);
if (!exists)
{
try { value = valueFactory(key); }
catch { dictionary.Remove(key); throw; }
}
return value;
}
Usage example:
List<Point> list = dictionary.GetOrAdd(pcost, key => new List<Point>());
list.Add(new Point { X = n.x, Y = n.y });
Online demo, featuring also an overload with generic parameter TArg
.
The try/catch in the implementation is required in order to remove the empty entry, in case the valueFactory
throws an exception. Otherwise the exception would leave the dictionary in a corrupted state (containing a key with a default
value).
Btw a proposal to add this method in the standard .NET libraries has been submitted on GitHub, but it didn't generate enough traction and it was closed.