Skip to content

Add CollectionsMarshal support for the HashSet<T> collection #82840

@theodorzoulias

Description

@theodorzoulias

Background and motivation

The Dictionary<TKey,TValue> collection was improved in .NET 6, by introducing direct ref access to its values. This improvement made the collection more suitable for performance-critical applications, by reducing the number of times a key has to be hashed when adding/modifying values in the collection. For example the GetOrAdd extension method can be implemented efficiently with a single hashing of the key. The same improvement could be made to the HashSet<T> collection as well. A GetOrAdd API has also been proposed for the HashSet<T>, but that proposal was closed with a mention about adding CollectionsMarshal support in the future. This proposal here is exactly about adding this support.

API Proposal

namespace System.Runtime.InteropServices
{
    public static partial class CollectionsMarshal
    {
        public static ref T? GetValueRefOrAddDefault<T> (HashSet<T> hashSet, T item, out bool exists);
        public static ref T GetValueRefOrNullRef<T> (HashSet<T> hashSet, T item);
    }    
}

API Usage

Below is a partial implementation of a concurrent HashSet<T> with bounded capacity. The proposed API GetValueRefOrAddDefault is used when space is available in the collection, and the proposed API GetValueRefOrNullRef is used when the collection is full:

public partial class ConcurrentBoundedHashSet<T>
{
    private readonly HashSet<T> _set = new();
    private readonly int _boundedCapacity;

    /// <summary>
    /// Searches the set for a given value and returns the equal value it finds,
    /// otherwise adds the given value to the set if space is available,
    /// otherwise returns false.
    /// </summary>
    public bool TryGetOrAdd(T equalValue, out T? actualValue)
    {
        lock (_set)
        {
            if (_set.Count < _boundedCapacity)
            {
                ref T? valueRef = ref CollectionsMarshal.GetValueRefOrAddDefault(_set, equalValue,
                    out bool exists);
                if (!exists) valueRef = equalValue;
                actualValue = valueRef; return true;
            }
            else
            {
                ref T valueRef = ref CollectionsMarshal.GetValueRefOrNullRef(_set, equalValue);
                if (!Unsafe.IsNullRef(ref valueRef))
                {
                    actualValue = valueRef; return true;
                }
                else
                {
                    actualValue = default; return false;
                }
            }
        }
    }
}

Alternative Designs

No response

Risks

None that I can think of.

Labels: area-System.Collections / api-suggestion.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions