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(); }