Search code examples
c#.netmarshallinghashset

How to implement HashSet.GetOrAdd(T) extension method in .NET 6 or .NET 7 using CollectionsMarshal


On dotnet runtime Github repo there was a proposal for adding HashSet.GetOrAdd(T) - similar to

public static T GetOrAdd<T>(this HashSet<T> hashSet, T equalValue)
{
    if (hashSet.TryGetValue(equalValue, out var actualValue))
        return actualValue;

    hashSet.Add(equalValue);
    return equalValue;
}

but without a duplicate hash lookup (performance impact).

That issue was closed with the following resolution:

We're going down CollectionsMarshal route for #15059, we should probably consider a similar approach for HashSet

In issue #15059 the last comment has an example of CollectionsMarshal usage for a Dictionary:

public static TValue GetOrAdd<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey key, Func<TKey, TValue> valueFactory)
where TKey : notnull
{
    if (dictionary == null)
        throw new ArgumentNullException(nameof(dictionary));

    if (valueFactory == null)
        throw new ArgumentNullException(nameof(valueFactory));            

    ref TValue? value = ref CollectionsMarshal.GetValueRefOrAddDefault(dictionary, key, out bool exists);
    if (!exists)
        value = valueFactory(key);

    return value!;
}

I've tried to use the same approach to implement HashSet.GetOrAdd(T) extension but failed - I don't have any idea how to get key value for CollectionsMarshal.GetValueRefOrAddDefault() method.

Both issues are locked as resolved and limited to collaborators - so I cannot ask directly there.


Solution

  • Answer from MS (only creating an extension method that performs double lookup is possible):

    For the record, using CollectionsMarshal for HashSet was proven to be not viable because you can't do ref returns on keys - #82840 (comment)

    That being said, consensus is that a GetOrAdd method for HashSet is not particularly valuable compared to just using HashSet.Add except for the rare scenario where you need to access the actual instance stored in the HashSet - #82875 (comment)