using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
///
/// Represents a thread-safe hash set, backed by a .
///
/// The type of elements in the set.
public partial class ConcurrentHashSet :
ICollection,
IReadOnlyCollection,
ICollection
where T : notnull
{
// The dummy value stored for every key — we only care about keys.
private static readonly byte DummyValue = 0;
private readonly ConcurrentDictionary _dictionary;
// -------------------------------------------------------------------------
// Constructors
// -------------------------------------------------------------------------
/// Initializes a new, empty instance using the default comparer.
public ConcurrentHashSet()
{
_dictionary = new ConcurrentDictionary();
}
/// Initializes a new instance that contains elements copied from the specified collection.
public ConcurrentHashSet(IEnumerable collection)
{
ArgumentNullException.ThrowIfNull(collection);
_dictionary = new ConcurrentDictionary(
collection.Select(item => new KeyValuePair(item, DummyValue)));
}
/// Initializes a new instance that contains elements copied from the specified collection
/// and uses the specified equality comparer.
public ConcurrentHashSet(IEnumerable collection, IEqualityComparer? comparer)
{
ArgumentNullException.ThrowIfNull(collection);
_dictionary = new ConcurrentDictionary(
collection.Select(item => new KeyValuePair(item, DummyValue)),
comparer);
}
/// Initializes a new, empty instance using the specified equality comparer.
public ConcurrentHashSet(IEqualityComparer? comparer)
{
_dictionary = new ConcurrentDictionary(comparer);
}
/// Initializes a new instance with the specified concurrency level, initial collection,
/// and equality comparer.
public ConcurrentHashSet(int concurrencyLevel, IEnumerable collection, IEqualityComparer? comparer)
{
ArgumentNullException.ThrowIfNull(collection);
_dictionary = new ConcurrentDictionary(
concurrencyLevel,
collection.Select(item => new KeyValuePair(item, DummyValue)),
comparer);
}
/// Initializes a new, empty instance with the specified concurrency level and initial capacity.
public ConcurrentHashSet(int concurrencyLevel, int capacity)
{
_dictionary = new ConcurrentDictionary(concurrencyLevel, capacity);
}
/// Initializes a new, empty instance with the specified concurrency level, initial capacity,
/// and equality comparer.
public ConcurrentHashSet(int concurrencyLevel, int capacity, IEqualityComparer? comparer)
{
_dictionary = new ConcurrentDictionary(concurrencyLevel, capacity, comparer);
}
// -------------------------------------------------------------------------
// Public properties
// -------------------------------------------------------------------------
/// Gets the number of elements contained in the set.
public int Count => _dictionary.Count;
/// Gets a value indicating whether the set is empty.
public bool IsEmpty => _dictionary.IsEmpty;
// -------------------------------------------------------------------------
// Public methods
// -------------------------------------------------------------------------
/// Removes all elements from the set.
public void Clear() => _dictionary.Clear();
/// Determines whether the set contains the specified element.
public bool Contains(T item)
{
if (item is null) throw new ArgumentNullException(nameof(item));
return _dictionary.ContainsKey(item);
}
/// Returns an enumerator that iterates through the elements of the set.
public IEnumerator GetEnumerator() => _dictionary.Keys.GetEnumerator();
///
/// Returns the element from the set if it already exists, or adds and returns
/// the specified item if it does not.
///
/// The element to get or add.
/// The existing element if found; otherwise after it was added.
public T GetOrAdd(T item)
{
if (item is null) throw new ArgumentNullException(nameof(item));
// TryAdd is atomic; if it fails the item was already present.
_dictionary.TryAdd(item, DummyValue);
// Because ConcurrentDictionary keys are de-duplicated by the comparer,
// we need to retrieve the canonical key that is actually stored.
// Keys returns a snapshot; iterate to find the stored instance.
foreach (T key in _dictionary.Keys)
{
if (_dictionary.Comparer.Equals(key, item))
return key;
}
// Fallback — should not happen in practice.
return item;
}
///
/// Adds the specified element to the set. Duplicate elements are silently ignored.
/// This overload exists to support collection initializer syntax (new ConcurrentHashSet<T> { item }).
///
public void Add(T item) => TryAdd(item);
/// Attempts to add the specified element to the set.
/// if the element was added; if it was already present.
public bool TryAdd(T item)
{
if (item is null) throw new ArgumentNullException(nameof(item));
return _dictionary.TryAdd(item, DummyValue);
}
/// Attempts to remove the specified element from the set.
/// if the element was removed; if it was not found.
public bool TryRemove(T item)
{
if (item is null) throw new ArgumentNullException(nameof(item));
return _dictionary.TryRemove(item, out _);
}
/// Copies the elements of the set to a new array.
public T[] ToArray() => [.. _dictionary.Keys];
/// Returns a (non-thread-safe) snapshot of the current elements.
public HashSet ToHashSet() => new(_dictionary.Keys, _dictionary.Comparer);
// -------------------------------------------------------------------------
// ICollection explicit implementation
// -------------------------------------------------------------------------
bool ICollection.IsReadOnly => false;
void ICollection.Add(T item) => Add(item);
bool ICollection.Contains(T item) => Contains(item);
void ICollection.CopyTo(T[] array, int index)
{
ArgumentNullException.ThrowIfNull(array);
// Take a snapshot to avoid races during copy.
T[] snapshot = ToArray();
Array.Copy(snapshot, 0, array, index, snapshot.Length);
}
bool ICollection.Remove(T item) => TryRemove(item);
// -------------------------------------------------------------------------
// ICollection (non-generic) explicit implementation
// -------------------------------------------------------------------------
///
/// does not expose a meaningful
/// sync root; following the same convention we return for
/// and for
/// , mirroring the BCL approach.
///
bool ICollection.IsSynchronized => false;
object ICollection.SyncRoot => this;
void ICollection.CopyTo(Array array, int index)
{
ArgumentNullException.ThrowIfNull(array);
if (array is T[] typedArray)
{
((ICollection)this).CopyTo(typedArray, index);
return;
}
// Slower path for object arrays (e.g. object[]).
T[] snapshot = ToArray();
Array.Copy(snapshot, 0, array, index, snapshot.Length);
}
// -------------------------------------------------------------------------
// IEnumerable explicit implementation
// -------------------------------------------------------------------------
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}