Last active
May 20, 2026 13:49
-
-
Save TheBigNeo/a2af79d1674314856032bc165c41c1ed to your computer and use it in GitHub Desktop.
ConcurrentHashSet
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #nullable enable | |
| using System; | |
| using System.Collections; | |
| using System.Collections.Concurrent; | |
| using System.Collections.Generic; | |
| using System.Diagnostics; | |
| using System.Linq; | |
| using JetBrains.Annotations; | |
| namespace Utils; | |
| /// <summary> | |
| /// Represents a thread-safe set of values that can be accessed by multiple threads concurrently.<br /> | |
| /// A set is a collection that contains no duplicate elements and whose elements are in no particular order. | |
| /// </summary> | |
| /// <seealso cref="HashSet{T}" /> | |
| /// <typeparam name="T">The type of elements in the hash set.</typeparam> | |
| [DebuggerDisplay("Count = {" + nameof(Count) + "}")] | |
| public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ICollection<T> | |
| where T : notnull | |
| { | |
| private const byte DummyByte = byte.MinValue; | |
| /// <summary> | |
| /// The underlying dictionary used to store elements. | |
| /// </summary> | |
| private readonly ConcurrentDictionary<T, byte> _dictionary; | |
| /// <summary> | |
| /// Returns the number of elements in the set. | |
| /// </summary> | |
| /// <remarks> | |
| /// To check for empty, use <see cref="IsEmpty" />. It has better performance. | |
| /// </remarks> | |
| public int Count => _dictionary.Count; | |
| /// <inheritdoc /> | |
| public bool IsReadOnly => false; | |
| /// <summary> | |
| /// Returns if the set is empty. | |
| /// </summary> | |
| public bool IsEmpty => _dictionary.IsEmpty; | |
| /// <summary> | |
| /// Initializes a new instance of the <see cref="ConcurrentHashSet{T}" /> class that is empty. | |
| /// </summary> | |
| public ConcurrentHashSet() | |
| { | |
| _dictionary = new ConcurrentDictionary<T, byte>(); | |
| } | |
| /// <inheritdoc /> | |
| public ConcurrentHashSet(ConcurrentHashSet<T> hashSet) | |
| : this(hashSet ?? throw new ArgumentNullException(nameof(hashSet)), hashSet.Count) | |
| { | |
| } | |
| /// <inheritdoc /> | |
| public ConcurrentHashSet(HashSet<T> hashSet) | |
| : this(hashSet ?? throw new ArgumentNullException(nameof(hashSet)), hashSet.Count) | |
| { | |
| } | |
| /// <inheritdoc /> | |
| public ConcurrentHashSet(IReadOnlyCollection<T> collection) | |
| : this(collection ?? throw new ArgumentNullException(nameof(collection)), collection.Count) | |
| { | |
| } | |
| /// <summary> | |
| /// Initializes a new instance of the <see cref="ConcurrentHashSet{T}" /> class that contains elements copied from the given <paramref name="collection" />. | |
| /// </summary> | |
| /// <param name="collection">The <see cref="IEnumerable{T}" /> whose elements are copied to the new <see cref="ConcurrentHashSet{TKey}" />.</param> | |
| /// <param name="capacity">The size of the <paramref name="collection" />.</param> | |
| /// <exception cref="ArgumentNullException"><paramref name="collection" /> is <c>null</c>.</exception> | |
| public ConcurrentHashSet(IEnumerable<T> collection, int capacity) | |
| { | |
| ArgumentNullException.ThrowIfNull(collection); | |
| _dictionary = new ConcurrentDictionary<T, byte>(Environment.ProcessorCount, capacity); | |
| foreach (T item in collection) | |
| { | |
| _dictionary.TryAdd(item, DummyByte); | |
| } | |
| } | |
| /// <inheritdoc cref="ISet{T}.Add" /> | |
| public bool Add(T item) => _dictionary.TryAdd(item, DummyByte); | |
| /// <inheritdoc /> | |
| void ICollection<T>.Add(T item) => Add(item); | |
| /// <inheritdoc cref="ISet{T}.Clear" /> | |
| public void Clear() => _dictionary.Clear(); | |
| /// <inheritdoc cref="ISet{T}.Contains" /> | |
| public bool Contains(T item) => _dictionary.ContainsKey(item); | |
| /// <inheritdoc /> | |
| public void CopyTo(T[] array, int arrayIndex) | |
| { | |
| ArgumentNullException.ThrowIfNull(array); | |
| ArgumentOutOfRangeException.ThrowIfNegative(arrayIndex); | |
| if (arrayIndex > array.Length) | |
| { | |
| string message = $"The {nameof(arrayIndex)} is out of bounds. Array Length: {array.Length}, Index: {arrayIndex}"; | |
| throw new ArgumentOutOfRangeException(nameof(arrayIndex), arrayIndex, message); | |
| } | |
| if (Count > array.Length - arrayIndex) | |
| { | |
| string message = $"Destination array is not long enough to copy all elements. Array Length: {array.Length}, HashSet Count: {Count}"; | |
| throw new ArgumentException(message, nameof(array)); | |
| } | |
| foreach (T key in _dictionary.Keys) | |
| { | |
| array[arrayIndex++] = key; | |
| } | |
| } | |
| /// <inheritdoc cref="ICollection{T}.Remove" /> | |
| public bool Remove(T item) => _dictionary.TryRemove(item, out _); | |
| /// <inheritdoc cref="ICollection{T}.GetEnumerator" /> | |
| public IEnumerator<T> GetEnumerator() => _dictionary.Keys.GetEnumerator(); | |
| IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); | |
| /// <summary> | |
| /// Creates a new list containing all elements from the set.<br /> | |
| /// The returned list is a snapshot of the elements. | |
| /// So this is <b>not</b> a reference that will be updated if the set changes. | |
| /// </summary> | |
| /// <returns>A new list containing all elements from the set.</returns> | |
| [MustUseReturnValue] | |
| public List<T> ToList() => _dictionary.Keys.ToList(); | |
| /// <summary> | |
| /// Determines whether the given <paramref name="other" /> has the same elements. <br /> | |
| /// Both must have the same number of elements and must contain the same elements.<br /> | |
| /// <b>The order of the elements is ignored.</b> | |
| /// </summary> | |
| /// <param name="other">The collection to compare with this set.</param> | |
| /// <returns> | |
| /// <c>true</c>, if the <paramref name="other" /> has the same elements.<br /> | |
| /// <c>false</c>, if <paramref name="other" /> is <c>null</c> or has any different element. | |
| /// </returns> | |
| /// <seealso cref="HashSet{T}.SetEquals" /> | |
| [MustUseReturnValue] | |
| public bool SetEquals(IReadOnlyCollection<T>? other) | |
| { | |
| if (other is null) | |
| { | |
| return false; | |
| } | |
| // A set is equal to itself. | |
| if (ReferenceEquals(other, this)) | |
| { | |
| return true; | |
| } | |
| if (Count != other.Count) | |
| { | |
| return false; | |
| } | |
| return _dictionary.Keys.All(other.Contains); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment