ConcurrentHashSet<T> Class
Definition
- Namespace
- Bodu.Collections.Generic.Concurrent
- Assembly
- Bodu.Collections.Concurrent.dll
- Package
- Bodu.Collections.Concurrent 1.0.0
Provides a thread-safe, unordered set of unique elements.
public sealed class ConcurrentHashSet<T> : ICollection, IReadOnlyCollection<T>, ISet<T>, ICollection<T>, IEnumerable<T>, IEnumerable where T : notnull
Type Parameters
TThe type of elements in the set (constraint:
where T : notnull).
- Inheritance
-
ConcurrentHashSet<T>
- Implements
-
ISet<T>ICollection<T>IEnumerable<T>
- Inherited Members
- Extension Methods
Examples
var seen = new ConcurrentHashSet<string>(StringComparer.OrdinalIgnoreCase);
Parallel.ForEach(urls, url =>
{
if (seen.Add(url))
Process(url); // runs once per distinct URL, even under concurrency
});
Console.WriteLine(seen.Count);
Remarks
ConcurrentHashSet<T> is a lock-free hash set built on a split-ordered list (Shalev & Shavit): every element lives in a single Harris-Michael lock-free ordered linked list, sorted by the bit-reversed hash code of the element. The hash-table part is only an array of lazily initialized shortcut pointers into that list - one sentinel node per bucket. Doubling the table copies shortcut pointers but never rehashes or moves a node, so growth is itself lock-free and readers are never invalidated by a resize.
No operation on this type ever takes a lock or blocks another thread. Add(T), Remove(T), Contains(T), Clear(), Count, and ToArray() are all lock-free: a suspended or preempted thread can never prevent other threads from completing their operations. Mutations are applied with single-reference compare-and-swap operations; deletion uses the standard two-phase Harris scheme (logical marking, then cooperative physical unlinking).
Add(T), Remove(T), and Contains(T) are each individually atomic (linearizable) and safe to call concurrently from any number of threads. Count and IsEmpty read a counter maintained with interlocked operations - exact whenever no mutation is in flight, and never off by more than the operations currently executing. Clear() atomically replaces the entire backing structure in one compare-and-swap; operations that began against the previous structure complete against it and are ordered before the clear.
ToArray() and enumeration are weakly consistent rather than point-in-time snapshots: the traversal observes every element that is present for the entire duration of the call and never throws, but elements added or removed while the traversal is in progress may or may not be observed, and the result is not guaranteed to correspond to the set's state at any single instant. The no-duplicate guarantee holds across distinct hash keys; an element that is concurrently removed and re-added may, in rare interleavings where another element shares its full hash key, be yielded twice. Consumers that require exact-once semantics should snapshot while the set is quiescent.
Performance characteristics differ from lock-based designs: progress is guaranteed without blocking, but every successful Add(T) allocates one list node and every successful Remove(T) allocates one short-lived marker (reclaimed by the garbage collector once unlinked). Contains(T) is allocation-free. Elements that share a hash code form a single linearly scanned run, so - as with any hash set - a comparer with poor hash distribution degrades lookups toward linear time.
The set implements the full ISet<T> contract. The bulk set-algebra operations it adds ( UnionWith(IEnumerable<T>), IntersectWith(IEnumerable<T>), ExceptWith(IEnumerable<T>), SymmetricExceptWith(IEnumerable<T>) and the subset/superset predicates) are not atomic: they are evaluated as a sequence of individual atomic operations over a weakly consistent snapshot, so a concurrent mutation may interleave and the result can reflect a state the set never occupied at any single instant. See each member's remarks for details.
The element type is constrained to notnull: both reference types and value types are supported, and
null is never a valid element. Element equality and hashing use the
IEqualityComparer<T> supplied at construction, or Default when
none is supplied.
Constructors
ConcurrentHashSet()
Initializes a new instance of the ConcurrentHashSet<T> class that is empty and uses the default equality comparer.
public ConcurrentHashSet()
ConcurrentHashSet(IEnumerable<T>)
Initializes a new instance of the ConcurrentHashSet<T> class containing the distinct elements
copied from collection, using the default equality comparer.
public ConcurrentHashSet(IEnumerable<T> collection)
Parameters
collectionIEnumerable<T>The collection whose distinct elements are copied into the set.
Exceptions
- ArgumentNullException
collectionis null.
ConcurrentHashSet(IEnumerable<T>, IEqualityComparer<T>?)
Initializes a new instance of the ConcurrentHashSet<T> class containing the distinct elements
copied from collection, using the specified equality comparer.
public ConcurrentHashSet(IEnumerable<T> collection, IEqualityComparer<T>? comparer)
Parameters
collectionIEnumerable<T>The collection whose distinct elements are copied into the set.
comparerIEqualityComparer<T>The comparer used to hash and compare elements, or null to use the default comparer.
Remarks
Elements that compare equal under comparer are collapsed to a single entry.
Exceptions
- ArgumentNullException
collectionis null.
ConcurrentHashSet(IEqualityComparer<T>?)
Initializes a new instance of the ConcurrentHashSet<T> class that is empty and uses the specified equality comparer.
public ConcurrentHashSet(IEqualityComparer<T>? comparer)
Parameters
comparerIEqualityComparer<T>The comparer used to hash and compare elements, or null to use the default comparer.
ConcurrentHashSet(int)
Initializes a new instance of the ConcurrentHashSet<T> class that is empty and sized for the specified expected number of elements using the default equality comparer.
public ConcurrentHashSet(int capacity)
Parameters
capacityintThe estimated number of elements the set will hold before its first resize.
Remarks
capacity is a sizing hint only, expressed in expected elements (matching
HashSet(int) and
ConcurrentDictionary<TKey, TValue>): it is converted internally to
a bucket count and the set grows automatically, so its size is not bounded by this value.
Exceptions
- ArgumentOutOfRangeException
capacityis negative.
ConcurrentHashSet(int, IEqualityComparer<T>?)
Initializes a new instance of the ConcurrentHashSet<T> class that is empty, sized for the specified expected number of elements, and uses the specified equality comparer.
public ConcurrentHashSet(int capacity, IEqualityComparer<T>? comparer)
Parameters
capacityintThe estimated number of elements the set will hold before its first resize.
comparerIEqualityComparer<T>The comparer used to hash and compare elements, or null to use the default comparer.
Remarks
capacity is a sizing hint only, expressed in expected elements (matching
HashSet(int) and
ConcurrentDictionary<TKey, TValue>): it is converted internally to
a bucket count and the set grows automatically, so its size is not bounded by this value.
Exceptions
- ArgumentOutOfRangeException
capacityis negative.
Properties
Comparer
Gets the equality comparer used to hash and compare elements.
public IEqualityComparer<T> Comparer { get; }
Property Value
- IEqualityComparer<T>
The active equality comparer.
Count
Gets the number of elements currently contained in the set.
public int Count { get; }
Property Value
- int
The element count observed at the moment of the call.
Remarks
Reading this property is lock-free - a single interlocked counter read that never blocks writers. The value is exact whenever no mutation is concurrently in flight; while Add(T) or Remove(T) calls are executing on other threads the value may transiently lag those operations, and it may already be stale by the time the caller inspects it.
IsEmpty
Gets a value indicating whether the set is empty.
public bool IsEmpty { get; }
Property Value
Remarks
Reading this property is lock-free and equivalent to comparing Count against zero. Under concurrent mutation the answer may be stale by the time the caller inspects it.
IsReadOnly
Gets a value indicating whether the set is read-only.
public bool IsReadOnly { get; }
Property Value
Methods
Add(T)
Adds the specified element to the set.
public bool Add(T item)
Parameters
itemTThe element to add.
Returns
Remarks
This operation is atomic and lock-free: the element is inserted into the split-ordered list with a single compare-and-swap, so the call never blocks and never prevents other threads from making progress. A lost compare-and-swap (a concurrent insert or delete at the same position) simply retries from a fresh traversal.
Exceptions
- ArgumentNullException
itemis null.
Clear()
Removes all elements from the set.
public void Clear()
Remarks
This operation is lock-free and linearizable: it atomically replaces the entire backing structure (list, bucket shortcuts, and counter) with a fresh empty one in a single compare-and-swap, which is the operation's linearization point. Operations that began against the previous structure complete against it and are ordered before the clear - an Add(T) that races the swap either lands in the new structure or is cleared along with the old one, exactly as if it had completed an instant before the clear.
The replacement keeps the current bucket count, so a previously grown set is not forced to immediately regrow.
Contains(T)
Determines whether the set contains the specified element.
public bool Contains(T item)
Parameters
itemTThe element to locate.
Returns
Remarks
This operation is a pure lock-free read: it walks the split-ordered list through volatile reads, hopping over logically deleted nodes without helping to unlink them, and never blocks or is blocked by a concurrent writer. The first lookup that targets an untouched bucket may lazily publish that bucket's shortcut sentinel.
Exceptions
- ArgumentNullException
itemis null.
CopyTo(T[], int)
Copies the elements of the set to the specified array, starting at the specified index.
public void CopyTo(T[] array, int arrayIndex)
Parameters
arrayT[]The destination array. Must not be null.
arrayIndexintThe zero-based index in
arrayat which copying begins.
Remarks
This method takes a weakly consistent snapshot of the set (see ToArray()) before copying. The destination is a fixed copy that is unaffected by concurrent modifications made after the snapshot completes, but it is not guaranteed to reflect the set's contents at any single instant.
Exceptions
- ArgumentNullException
arrayis null.- ArgumentOutOfRangeException
arrayIndexis less than zero.- ArgumentException
The number of elements in the set exceeds the available space in
arrayfromarrayIndexonward.
ExceptWith(IEnumerable<T>)
Modifies the set so that it contains only elements that are not present in other.
public void ExceptWith(IEnumerable<T> other)
Parameters
otherIEnumerable<T>The collection of elements to remove from the set.
Remarks
This operation is not atomic. It is applied as a sequence of individual atomic Remove(T) calls; a concurrent mutation may interleave with those calls.
Exceptions
- ArgumentNullException
otheris null.
GetEnumerator()
Returns an enumerator that iterates over a weakly consistent snapshot of the set.
public ConcurrentHashSet<T>.Enumerator GetEnumerator()
Returns
- ConcurrentHashSet<T>.Enumerator
An ConcurrentHashSet<T>.Enumerator over the elements present when the enumerator was created.
Remarks
Enumeration operates on a weakly consistent snapshot captured via ToArray() at the moment this method is called. Elements added or removed after the snapshot completes are not reflected in the enumerated sequence; elements mutated while the snapshot is being captured may or may not appear.
Because the enumerator operates on a snapshot, it never throws InvalidOperationException due to concurrent modification - unlike enumerators on non-concurrent collections.
IntersectWith(IEnumerable<T>)
Modifies the set so that it contains only elements that are also present in other.
public void IntersectWith(IEnumerable<T> other)
Parameters
otherIEnumerable<T>The collection to intersect with the set.
Remarks
This operation is not atomic. It materializes other, then removes the non-matching
elements of a weakly consistent snapshot through individual atomic Remove(T) calls. A concurrent
mutation may interleave, so the resulting set may not equal the intersection of any single observed state.
Exceptions
- ArgumentNullException
otheris null.
IsProperSubsetOf(IEnumerable<T>)
Determines whether the set is a proper (strict) subset of other.
public bool IsProperSubsetOf(IEnumerable<T> other)
Parameters
otherIEnumerable<T>The collection to compare against.
Returns
Remarks
This operation is not atomic. It compares a weakly consistent snapshot of the set against
other; a concurrent mutation may interleave, so the result describes a relationship that may
no longer hold when the call returns.
Exceptions
- ArgumentNullException
otheris null.
IsProperSupersetOf(IEnumerable<T>)
Determines whether the set is a proper (strict) superset of other.
public bool IsProperSupersetOf(IEnumerable<T> other)
Parameters
otherIEnumerable<T>The collection to compare against.
Returns
Remarks
This operation is not atomic. It compares a weakly consistent snapshot of the set against
other; a concurrent mutation may interleave, so the result describes a relationship that may
no longer hold when the call returns.
Exceptions
- ArgumentNullException
otheris null.
IsSubsetOf(IEnumerable<T>)
Determines whether the set is a subset of other.
public bool IsSubsetOf(IEnumerable<T> other)
Parameters
otherIEnumerable<T>The collection to compare against.
Returns
Remarks
This operation is not atomic. It compares a weakly consistent snapshot of the set against
other; a concurrent mutation may interleave, so the result describes a relationship that may
no longer hold when the call returns.
Exceptions
- ArgumentNullException
otheris null.
IsSupersetOf(IEnumerable<T>)
Determines whether the set is a superset of other.
public bool IsSupersetOf(IEnumerable<T> other)
Parameters
otherIEnumerable<T>The collection to compare against.
Returns
Remarks
This operation is not atomic. It tests each element of other against the set through
individual lock-free Contains(T) calls; a concurrent mutation may interleave, so the result
describes a relationship that may no longer hold when the call returns.
Exceptions
- ArgumentNullException
otheris null.
Overlaps(IEnumerable<T>)
Determines whether the set and other share any elements.
public bool Overlaps(IEnumerable<T> other)
Parameters
otherIEnumerable<T>The collection to compare against.
Returns
Remarks
This operation is not atomic. It tests each element of other against the set through
individual lock-free Contains(T) calls; a concurrent mutation may interleave.
Exceptions
- ArgumentNullException
otheris null.
Remove(T)
Removes the specified element from the set.
public bool Remove(T item)
Parameters
itemTThe element to remove.
Returns
Remarks
This operation is atomic and lock-free. Removal is two-phase in the Harris style: the node is first logically deleted by installing a marker into its successor link with a compare-and-swap (the linearization point), then physically unlinked best-effort - any traversal that later encounters the marker completes the unlinking cooperatively.
Exceptions
- ArgumentNullException
itemis null.
SetEquals(IEnumerable<T>)
Determines whether the set contains exactly the same elements as other.
public bool SetEquals(IEnumerable<T> other)
Parameters
otherIEnumerable<T>The collection to compare against.
Returns
- bool
true if the set's snapshot and
othercontain the same elements (ignoring duplicates and order); otherwise, false.
Remarks
This operation is not atomic. It compares a weakly consistent snapshot of the set against
other; a concurrent mutation may interleave, so the result describes a relationship that may
no longer hold when the call returns.
Exceptions
- ArgumentNullException
otheris null.
SymmetricExceptWith(IEnumerable<T>)
Modifies the set so that it contains only elements that are present either in the set or in
other, but not in both.
public void SymmetricExceptWith(IEnumerable<T> other)
Parameters
otherIEnumerable<T>The collection to apply the symmetric difference with.
Remarks
This operation is not atomic. It materializes the distinct elements of other, then
toggles each one through individual atomic Add(T) and Remove(T) calls. A concurrent
mutation may interleave, so the resulting set may not equal the symmetric difference of any single observed
state.
Exceptions
- ArgumentNullException
otheris null.
ToArray()
Copies the elements of the set into a new array.
public T[] ToArray()
Returns
- T[]
A new array containing a weakly consistent snapshot of the set's elements, or an empty array if no elements were observed. The order of elements is unspecified.
Remarks
This method is a pure lock-free traversal of the split-ordered list: it never blocks writers. The result is weakly consistent - it contains every element that was present for the entire duration of the call, but elements added or removed while the copy is in progress may or may not appear, and the array is not guaranteed to reflect the set's state at any single instant.
The traversal is monotonic in split-order, so an element whose full hash key is unshared can never be visited twice. Within a run of elements sharing the same full hash key, however, a concurrent remove followed by a re-add of an element can reinsert its node ahead of the traversal cursor, so in rare full-hash-collision interleavings such an element may appear twice in the result. Consumers that require exact-once semantics should take the snapshot while the set is quiescent.
UnionWith(IEnumerable<T>)
Modifies the set so that it contains every element that is present in either the set or
other.
public void UnionWith(IEnumerable<T> other)
Parameters
otherIEnumerable<T>The collection of elements to add to the set.
Remarks
This operation is not atomic. It is applied as a sequence of individual atomic Add(T) calls;
a concurrent mutation from another thread may interleave with those calls, so the resulting set may not equal
the union of any single observed state of the set with other.
Exceptions
- ArgumentNullException
otheris null.
Explicit Interface Implementations
ICollection<T>.Add(T)
Adds the specified element to the set through the Add(T) contract.
void ICollection<T>.Add(T item)
Parameters
itemTThe element to add.
Remarks
Discards the boolean result of Add(T); callers that need to detect a duplicate add should invoke the public Add(T) overload directly.
Exceptions
- ArgumentNullException
itemis null.
IEnumerable<T>.GetEnumerator()
Returns an enumerator that iterates over a weakly consistent snapshot of the set.
IEnumerator<T> IEnumerable<T>.GetEnumerator()
Returns
- IEnumerator<T>
An ConcurrentHashSet<T>.Enumerator over the elements present when the enumerator was created.
Remarks
Enumeration operates on a weakly consistent snapshot captured via ToArray() at the moment this method is called. Elements added or removed after the snapshot completes are not reflected in the enumerated sequence; elements mutated while the snapshot is being captured may or may not appear.
Because the enumerator operates on a snapshot, it never throws InvalidOperationException due to concurrent modification - unlike enumerators on non-concurrent collections.
ICollection.CopyTo(Array, int)
Copies the elements of the ConcurrentHashSet<T> to a one-dimensional, zero-based Array, starting at the specified index.
void ICollection.CopyTo(Array array, int index)
Parameters
arrayArrayThe destination array. Must not be null, must be single-dimensional, and must have zero-based indexing.
indexintThe zero-based index in
arrayat which copying begins.
Remarks
This method takes a weakly consistent snapshot of the set (see ToArray()) before copying. The destination array is a fixed copy that is not affected by concurrent modifications made after the snapshot completes, but it is not guaranteed to reflect the set's state at any single instant.
Exceptions
- ArgumentNullException
arrayis null.- ArgumentException
arrayis multidimensional, does not have zero-based indexing, its element type is incompatible withT, or the number of elements in the set exceeds the available space fromindexto the end ofarray.- ArgumentOutOfRangeException
indexis less than zero.
ICollection.IsSynchronized
Gets a value indicating whether access to the ConcurrentHashSet<T> is synchronized (thread safe).
bool ICollection.IsSynchronized { get; }
Returns
- bool
Always false. ConcurrentHashSet<T> manages its own internal synchronization and does not expose a public lock object.
Remarks
Thread safety is achieved through a lock-free algorithm - there is no internal lock to expose. Callers should not attempt to coordinate access externally via SyncRoot, as that property is not supported.
ICollection.SyncRoot
Gets an object that can be used to synchronize access to the collection. Not supported on this type - ConcurrentHashSet<T> manages its own internal synchronization.
object ICollection.SyncRoot { get; }
Returns
Remarks
The set is lock-free, so no lock object exists that external code could meaningfully share; exposing one would only invite callers to serialize access the algorithm does not require. This matches the behavior of ConcurrentDictionary<TKey, TValue> and other BCL concurrent collections.
Exceptions
- NotSupportedException
Always thrown. Use the thread-safe members of this class directly.
IEnumerable.GetEnumerator()
Returns an enumerator that iterates over a weakly consistent snapshot of the set.
IEnumerator IEnumerable.GetEnumerator()
Returns
- IEnumerator
An ConcurrentHashSet<T>.Enumerator over the elements present when the enumerator was created.
Remarks
Enumeration operates on a weakly consistent snapshot captured via ToArray() at the moment this method is called. Elements added or removed after the snapshot completes are not reflected in the enumerated sequence; elements mutated while the snapshot is being captured may or may not appear.
Because the enumerator operates on a snapshot, it never throws InvalidOperationException due to concurrent modification - unlike enumerators on non-concurrent collections.
Applies to
| Product | Versions |
|---|---|
| .NET | 8, 10 |